DynamicのついでにILの話したついでに、ネイティブと.NETの対比の話もしましょうか。
ここでいうネイティブは、実CPUのネイティブ コード(を直接出力するC++などの言語)のことです。
ネイティブか.NETか、それが問題だ
嘘です。
「AかB、どっちがいい?」って質問の答えは、往々にして「両方」です。残念ながら。毎回毎回、「これ1つですべてを解決!」みたいな銀の弾丸を期待しては、毎回毎回、夢物語に終わるのです。
アプリは.NET(C#とか)で書いた方が楽。それは間違いないです。一方で、インフラ的なところ、データベースやらGUIフレームワークみたいなのの内部はネイティブで書いた方が楽。場合によってはネイティブで書かざるを得ません。極度のパフォーマンスが必要な場面では、.NETで書く方が却って大変になることが多いです。
結局、適材適所としか言いようがないです。じゃあ、それぞれどこが適所なんでしょう?それぞれの利点/欠点をまとめてみましょう。
3つの視点
.NET Frameworkが持っている性質を3つに分けて考えてみます。
- 共通型システム
- メモリ管理の自動化
- CPU独立な中間言語
ネイティブとマネージ(managed: .NET Frameworkに管理されているという意味)の対立構造で描かれることが多くて、マネージ コードが持つこの3つの性質はまとめて説明されることが多いですが、互いに独立(にもできる)概念です。
共通型システムを持つことはネイティブな言語でも重要だと思いますし、メモリ管理が手動な中間言語があってもいいと思います。
共通型システム
.NETのライブラリは、C#からでもVBからでも、なんなら、PowerShellやIronPythonなどからでも共通利用可能です。これが可能なのは、共通型システム(common type system)という、型の取り扱いに関する規格を持っているからです。
一番大事
個人的には、.NETの性質のなかで一番大事なものだと思います。
今はもう、ライブラリやフレームワークなくしてアプリなんて作れません。.NETやJavaを見ての通り、標準ライブラリの時点で膨大な量のライブラリがあり、さらに、第3者提供のライブラリも山ほど見つかります。
これだけ大量のライブラリがあって、使いこなすことが要求されると、プログラミング言語自体を覚えることよりも、ライブラリを覚えるコストの方がはるかに大きいです。逆にいうと、ライブラリさえ共通利用できるなら、言語の違いなんて微々たるものです。
Windows 8
Windows 8で、共通型システムの適用範囲が広がります。.NETのメタデータ規格を流用して、ネイティブやJavaScriptでも型を共通利用できるようになります。
Windows 8では、今までのWPFのようなものが、ネイティブ コードで書きなおされました。しかし、新しい共通型システムのおかげで、.NETから見ると、今までと何の変りもなくWPFとまったく同じ感覚で使えます。
利点/欠点 まとめ
共通型システムの利点
- ライブラリの学習が楽になります
-
実行に関係ないメタデータを持つことで:
- 動的コード生成できる
- セキュリティに関する情報も持てる
共通型システムの欠点
- メタデータの分、プログラム サイズが大きくなる
-
標準規格に縛られる
- .NETとかJava仮想マシンの型システムはOOP前提なわけですが、他のパラダイムを使いたいときにネックにならないか?
- Java仮想マシンはジェネリックや値型を持っていないのが結構なネックに
- 「nullを許さない参照型」が欲しかったりするけども、今から追加するにはコストが大きすぎる
メモリ管理自動化
あなたのやりたいことはメモリ管理ですか?
メモリ管理自体が目的になることはないわけですが、その割に、メモリ管理は非常に大変です。
今となっては、むしろ、メモリの自動管理機構を持っていない言語の方が珍しくなってしまいましたねぇ。
マネージ ヒープ
.NETのように、ガベージ コレクション(garbage collection)機能によって管理されたメモリをマネージ ヒープ(managed heap)と言います。
マネージ ヒープは以下のような性質があります。
-
マネージ ヒープは、全体としてのスループット的には非常に性能がいいです
- ヒープ(動的なメモリ確保)が必要なら、素直にマネージ ヒープに任せる方がいいです
- ただ、処理負荷がある1点に集中してしまうことがあります
-
手動管理ではそもそもヒープを使わないような最適化が可能ですが、自動管理の場合はそういう最適化がしにくいです
- 下手なことをすると、ガベージ コレクションの仕事を阻害して、かえって遅くなります
-
マネージ ヒープは、確保できる物理メモリ量が多めにある時に良い性能を発揮します
- 省メモリ環境は苦手です
- 物理メモリを目いっぱい使うようなキャッシュ処理は苦手です
管理外リソース
さて、プログラムで使うリソースは、何もメモリばかりではありません。ファイルやグラフィック、ネットワーク接続なんかもあります。これらのリソース管理は、結局自前で行う必要があります。
最悪、メモリの自動解放のタイミングで一緒にこの手のリソースを解放してもいいんですが、無駄に多くのリソースを使ってしまうので、場合によってはかなり性能を落とします。
管理されててもメモリ リーク
ガベージ コレクションは、「誰からも参照されていないゴミを見つけて解放する」というような仕組みで動いています。なので、「本当はもう不要になったのに、誰かがずっと参照しっぱなし」みたいなことをすると、結局、不要なメモリが残り続けます。
イベント駆動なプログラム(GUIアプリなんかだとイベント駆動がほぼ必須)では、参照関係が複雑になりがちで、誰が誰を参照しているかがわからなくなりがちです。参照を外し忘れてメモリ リークしてしまうことも多いです。
利点/欠点 まとめ
メモリ自動管理の利点
- やりたいことに集中できる
- セキュリティ ホールを作りにくい
- ヒープの性能(スループット)が良い
- メモリ以外のリソースの管理は結局自前
メモリ自動管理の欠点
-
自前管理との相性があまり良くない
- 部分的に自前管理で性能上げようとかすると逆効果になりがち
- そもそもヒープ利用を避ける最適化もしづらい
- ガベージ コレクション発生時に一時的に応答悪くなることがある
中間言語
昨日、中間言語(IL: intermediate language)の説明をしました。要は、いったんCPU非依存な形式で配布しておいて、実行時に実CPUのネイティブ コードに変換します。
クロスCPU
.NETは、結構いろいろな環境で動くことを想定しています。デスクトップ向けのWindows以外にも、Windows Phone 7はARMですし、Xbox 360のCPUはPowerPCベースです。SilverlightにはMac版もあります。今は、monoもあるので、Linuxなどでも動きます。
同じOSであっても異なるCPUに対応しないといけません。いわゆる32ビット版(x86)と64ビット版(x64)がありますし、Windows 8ではARMプロセッサーにも対応します。
アプリを作る側からしても、それぞれのCPU向けにコンパイルして配布するのは結構面倒です。
コンパイラーを作る側の視点だと中間言語の重要性はもっと増します。プログラミング言語(C#とかVB)の専門家と、CPU毎の最適化の専門家に分かれて作業ができます。プログラミング言語の専門家は中間言語を作るところまで、CPU最適化の専門家は中間言語から先だけ気にすればよくなります。
セキュリティ
中間言語は、命令がかなり高級で、セキュリティ的な検証がしやすいです。.NETの場合は、署名を入れて検証したり、メタデータに必要な権限の情報も入れておけるので、かなり強固なセキュリティ保証ができます。
性能の良し悪し
平均的な状況では、.NETのコードでもネイティブの8~9割の性能が出るといわれています。ただ、ほんとに状況次第です。ネイティブの方が数倍早くなるような状況もありますし、.NETの方が早くなることもあります。
標準C++の範囲のコードならともかく、インライン アセンブラーを使ったCPU依存の最適化を駆け出すと、明らかにネイティブの方が早くなります。そこまでコストをかけてでも性能が必要な場合、.NETは使えません。
また、最近だと、大規模データ処理を、GPUを使って行うような手法(GPGPU: General-purpose computing on GPU)なんかもありますが、これも過度に環境依存するので、.NETからは直接活用しにくいです。
利点/欠点 まとめ
中間言語の利点
-
配布が容易
- 1バイナリでいろんなCPU向けに配布可能
- 命令長が短く、プログラム サイズが小さくなる
- セキュリティ保証しやすい
中間言語の欠点
-
多少、性能が犠牲に
- JITが挟まる
- CPUに過度に依存する(SIMD系命令とかGPGPUとか)最適化が無理