BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル Google Dartのエッセンス:アプリケーションの構築、スナップショット、Isolate

Google Dartのエッセンス:アプリケーションの構築、スナップショット、Isolate

原文(投稿日:2011/10/24)へのリンク

数千のプログラム言語がある中、なぜGoogleはGoogle Dartを提供したのだろう?これで、なにができるようになるのだろう?短い答えとして:Google Dartチームは、サーバー(モバイル)クライアントの両方に適した、モダンなアプリケーションを開発できる言語を望んでいた。

いくつかのDartの機能は、JavaやJavascriptのような言語が長い間抱えているような問題に対処している。Dartのスナップショットは、イメージがもたらすいくつかの問題を除いて、迅速にアプリケーションの起動を(ほぼ)可能にするSmalltalkのイメージに似ている。JavascriptのWebWorkersやErlangの処理のように、メッセージを投げて、並列に処理する、シェアードナッシングで、コードを単一スレッドに維持するように分離する(Isolates) 。いくつかの言語機能と決定は、モジュラー開発で、スケーラブルを可能にする。Dartは、DartCコンパイラやDart VM上で実行するように、プレーンなJavascriptをコンパイルすることができる。

InfoQはDartで最も興味深い、Dart VMとアプリケーション開発におけるいくつかの特筆すべき言語機能に注目した。

Dartはアプリケーション言語:スナップショットと初期化

アプリケーションの起動時間は、本当に関連するだろうか?1日の中でユーザーは、どれぐらいの頻度でIDEやワープロを再起動するだろうか? メモリの制約があるモバイルデバイスにおいては、アプリケーションの起動には多くのことが発生する; モバイルOSのOut Of Memory (OOM)キラープロセスは、休止中のアプリケーションを躊躇なく終了させる。iOSのマルチタスクモデルと目立つ物理的なボタンは、モバイルアプリの平均寿命を短くしている。iOS 4以前は、ホームボタンを押すと実行中のアプリケーションは常に終了していた。iOS 4では状況は少し複雑になったが、ユーザーの手動やOOMプロセスによる突然の死に、アプリケーションは継続して備えなくてはならない。
この振る舞いは、携帯OSにとどまらない。"突然の終了"と"自動終了"は、最新のOS Xバージョンで導入されたアプリケーションのプロパティで、ユーザーに透過的に、アプリケーションがどこでも(たとえば、メモリの空きが少ないとき)死を処理できるように定義され、再起動する

遅い起動と言えば、Java GUIアプリケーションは、Java 1.0からの悩みだ。巨大なJavaアプリケーションを起動することは、非常に大変な作業である。数千のクラスが読み込まれ、解析され、リンクされる。Java 1.6以前では、バイトコード検証メソッドのスタックマップ生成プロセスに含まれていた。そして、一度クラスが読み込まれると、スタックイニシャライザの実行を含めた初期化が必要になる。

これは、モダンなJava GUIアプリケーションで単にGUIを初期表示するための作業である。Java 6では、SplashScreen APIが提供され、開発者とそのユーザーに影響を与えている解決されていない問題であることを示した。

スナップショット vs Smalltalkイメージ

Dartは、Smalltalkのイメージシステムに似たヒープスナップショット機能でこの問題に対処している。アプリケーションのヒープは、すべてのオブジェクトがファイルに書き込まれる。現時点でDartは、Dart VMを起動し、アプリケーションコードを読み込んで、mainを呼び出す直前にヒープのスナップショットを取るツールと一緒に配布されている。Dart VMは、アプリケーションの読み込みを迅速にするためにスナップショットファイルを使うことができる。

スナップショット機能は、Dart VMのIsolate間でオブジェクトグラフを送信するためにシリアライズするためにも使用される。

Dartの初期技術プレビューでは、その根本的な理由はないはずだが、スナップショットを初期化するDart言語APIは存在していないように見える。

スナップショットの技術詳細

Dartチームはスナップショットのフォーマットに労力を注いだ。まず、これは32ビットであっても、64ビットであっても、それ以外であっても、マシン間を移動することができる。 フォーマットはまた、ポインタの習性のような追加の作業を最小限にすることに注力して、迅速にメモリに読み込めるように作られている。
完全なスナップショットの書き出し、それらの読み戻し、スナップショットからIsolatesの開始など、スナップショットシステムの使用に関する詳細は、runtime/vm/snapshot.ccruntime/vm/snapshot_test.ccを見て欲しい。

スナップショット vs Smalltalkイメージ

Smalltalkのイメージは、一般的に広まってはいない。Gilad Bracha氏は、実際面のSmalltalkイメージの問題について書いた。 Smalltalk開発は通常、未使用のコードを除いて配置用に凍結されたイメージに行われる。Dartのスナップショットはこれとは異なり、それらは任意で、アプリケーションの読み込み時に生成され、スナップショットを取る必要がある。Dartは、動的なコードの評価と、他のコードの読み込み機能が欠けており、ストリップ処理をより徹底することができるようになる。

DartCでコンパイルされたJavascriptのコードは、現在Dartスナップショットではサポートされていない。

現在、スナップショットはIsolate間のメッセージ渡しに使われている。オブジェクトは、SnapshotWriterでシリアライズされたメッセージとして送信され、もう一方で読み込まれる。

いずれにしろ、Dartの他の多くの機能と同様に、スナップショット機能はDart VMとツールにあり、この使い方を考えるコミュニティ次第である。

最後に、スナップショット機能は既に現在のGoogleのV8にあり、スナップショットからJavascript標準ライブラリを読み込む起動速度を改善するために使用されている。

初期化

スナップショットを除いても、Dartは可能であれば起動時に初期化を回避するように設計されている。クラスは宣言型であり、それらを作成するためには、なんのコードも実行されない。ライブラリは、トップレベルエレメントfinalとして定義されているため、関数と変数はクラスの外にあるが、それらはコンパイル時に定数である必要がある(言語仕様書セクション10.1を参照)。

これとJavaや起動時にデータ構造、オブジェクトシステムなどを生成する、様々なメタプログラミングメソッドを頼りにする言語で静的初期化子を比較してみよう。Dartは、アプリケーションの起動が迅速になるように最適化されている。
Dartは現時点では、リフレクションのメカニズムを持っていないが、Mirrors (PDF)をベースとしたものが、近い将来に言語でサポートされ、APIを使ったコードの構築とそれを新しいIsolateに読み込むことができるようになることで、Dartにメタプログラミングをもたらす。

並列性のユニット、セキュリティ、アプリケーション:Isolates

並列性

Dartの並列性の基本ユニットはIsolateである。すべてのIsolateは、シングルスレッドである。バックグラウンド、複数コアやCPUでの動作には、新しいIsolateの起動が必要である。
Google V8はまた先日、Isolatesを実装し、同じOSプロセスでこれらを起動することによって、より安くWebワーカーを実装することができ、V8を組み込む開発者にとってもっとも興味深い機能になった。この機能は、Javascriptに提供されていない。

並列のための複数の独立したIsolateを持つモデルは、JavascriptやErlangに似ている。Node.jsはまた、ひとつのCPUやコア以上を使うようにプロセスを使う必要がある。Node.jsプロセスを管理するソリューションのホストが起動される。

他のシングルまたは、グリーンスレッド言語は、同様のプロセスを集めるソリューションを持っている。 RubyのPhusion Passengerは、複数のプロセスで同じコードを読み込むときのオーバーヘッド問題を修正しようとした例である: Phusion Passengerは、Railsアプリケーションを読み込んで、その後OSのfork呼び出して同じプログラムコンテンツで複数のプロセスを迅速に作るため、同じアプリケーションを何度も解析して、初期化するのを避けることができる。Dartのスナップショット機能は、問題を他の方法で解決する。

信頼性

Dartの最初の技術レビューは、Isolateごとにひとつのスレッドを使っており、ひとつのスレッド上に多重化された複数のIsolateや、他のマシン上で実行することができるIsolateが他のOSプロセスで実行されると言った、他のモデルが検討されている。
アプリケーションを独立したプロセス(やIsolate) に分割することは、信頼性を高める:あるIsolateがクラッシュしても、他のIsolateは影響を受けずIsolateがクリーンに再起動することが可能だ。Erlangのモデルであるスーパービジョンツリーは、プロセスグループの死活監視と、それらの死を処理するカスタムポリシーを書くことが可能である。
このAkkaとErjangのクリエーターとのインタビューは、Erlangのモデルの利点の概要を提供してくれた。

セキュリティ

信頼されないコードは、それ自身のIsolateで実行することができる。こすべての通信は、対話できるIsolateのポートを制限したcapability-styleメカニズムで拡張したメッセージパスによって行う必要がある。Isolateは、メッセージを送信するポートを提供する必要があり、それを除いてはなにもすることができない。

メモリの区画化

アプリケーションをIsolateに分割するその他のメリットは:それぞれのIsolateのヒープは、独立している。すべてのオブジェクトは、明確にそこにだけ属しており、共有メモリ環境のオブジェクトとは切り離されている。 主なメリット:タスクのためにIsolateを起動してそれが完了した場合、すべてのIsolateは解放されGCの実行は必要ない。

さらに: アプリケーションがIsolateに分割された場合、アプリケーションの使うメモリは、小さな ヒープに分割されるため、アプリケーションが使う合計のメモリ量は小さくなる。それぞれのヒープは、それ自身のGCで管理されるため、完全なGCがあるIsolateで実行されたとして、Isolateの中が止まるだけで、他のIsolateは気づかないだろう。 GUIアプリだけでなく、GC停止に敏感なサーバーアプリケーションに対してもよいニュースだ: 時間に敏感なコンポーネントはひとつまたは複数のGCを忙しくするやっかいなゴミを吐き出すIsolateによる影響を受けない

Javaや.NETのGCは大幅に改善されているが、GCによる停止はGUIアプリケーションと時間に敏感なサーバーアプリケーションに取って、今でも重要な問題だ。停止を管理したり、ほぼ消去すると言ったAzulのCGのような解決策があるが、これには特別なハードウェアや彼らのx86ベースのZingのような低レベルOSインフラが必要になる。リアルタイムGCは存在しているが、それらも予測可能な停止によるスローダウンと交換である。
メモリを分割したヒープに分離することは、CG実装をシンプルにでき、十分に早くすることができることを意味する。もちろん、すべては開発者に依存している。これらの特徴の恩恵を受けるには、アプリケーションは複数のIsolateに分割する必要がある。

これ以上の依存性注入はしない: Dartのインターフェイスとファクトリ

"インターフェイスへのプログラム"は、一般的なアドバイスだが、実際には、誰かが本当のクラス名でnewを呼び出すことはちょっと難しい。Javaの世界では、この問題は依存性注入(DI)フレームワークの作成に導かれる。DIフレームワークの採用の最初の意味は、プロジェクトに明示的なDIフレームワーク上の依存性を注入することであった。
DIはどんな問題を解決するのか?明示的にクラスをハードコードして、newを呼び出すと、テストとコードの柔軟性に問題を引き起こす。 結局の所、すべてのコードがインターフェイスに向けて書かれたとしても、明示的な実装は問題ではなく、誰かがユースケースに対する正しい実装を選択するべきである。

Dartは現在、あるDIソリューションと共にリリースされており、他のオプションのホストから選択する必要がない。これはインターフェイスとコードをリンクしている言語でそうしているように、これのためにオブジェクトをインスタンス化できる。すべての柔軟性は、必要に応じてファクトリで非表示することができ、どのクラスをインスタンス化するかを決めようが、新しいオブジェクトが作られようが、ただキャッシュされたオブジェクトを返す。
インターフェイスはライブラリで提供している名前でファクトリから参照することができる。ファクトリの別実装に自前のライブラリを置くことができ、最適な実装に含めるかどうかは開発者が決めることができる。

言語

Google Dartは新しい言語だが、多くの開発者になじみがあるように設計されている。この言語は、インターフェイスにフォーカスしたOOPで、波括弧の言語に似ている。 DartのOOPシステムは、クラスであり、Clojure (プロトコルと型を組み合わせたOOP)やGoogle Go (Goはインターフェイスを持っているが、クラスはない)のような最近の他の言語とは異なる。言語にOOPシステムを組み上げる利点のひとつは、Javascriptのように他で使われているライブラリで新しいOOPシステムやパラダイムを取る必要がないことである。
詳細は、Dart Webサイトの公式のDart言語仕様や簡単な概要は、'Dartの特徴で見ることができる。

モジュール方式

Dartの名前空間は、クラス名がメソッドや変数名のように名前空間が必要なものに繋がる唯一の方法であるJavaとは違い、ライブラリメカニズムで完結している。結論:Dartのライブラリには、トップレベルにクラス以外(たとえば、クラスに属さない変数と関数)を含めることができる。

print関数は、クラスなしのトップレベル関数の例のひとつである。ライブラリシステムは、また名前の衝突を避ける解決策も提供している。ライブラリAは他のライブラリBからインポートすることができ、AとBの名前の衝突を避けるため、Bからインポートされたすべての名前にプレフィックスをつけることができる。たとえば、#import("foo.dart", "foo")は、ライブラリをインポートして、すべてのエレメントは、"foo."プレフィックスで利用可能になる。

任意の型付け(Optional Typing)

キーワード"Optional Typing"は、"Optional(任意)"である。開発者は、型アノテーションをコードに追加することができるが、このアノテーションは、コードの振る舞いに影響を与えない。実際のところ、これには無意味な型を指定したとしても実行には影響がない。

コード内に型を持つことは、様々な型チェッカーを動かすことになる。Dartに付属するエディタは、型チェッカーと型エラーと警告をハイライトすることができる。Dartの型アノテーションは、チェックモードになり、コードのチェックと違反を警告やエラーとしてレポートする。

任意の型アノテーションは、ドキュメント化の目的に便利な型情報をコードに持つことができる。受け入れ可能なダックを考慮したメソッドのリストを実装した引数を説明するドキュメントをこれ以上探す必要がない。インターフェイスの存在は、すなわちメソッドのシグニチャを持つメソッドのセットの名前と、任意の型アノテーションは、ドキュメントAPIを可能にする。
重要なのは、言語は常に動的で引数はDynamic型として、動的と指定することができる。

ランタイム拡張性と可変性 - またはその欠如

脇道にそれてみよう: モンキーパッチをしない。evalをしない。現時点では、ミラーベースシステム(詳細はこのミラーを紹介した文書を見て欲しい)は作業中だが、リフレクションはない。計画はIsolateに対する新しいコードの構築に制限がかかっているように見え、現在実行中のプロセスはない。

noSuchMethod

Dartは、Rubyのmethod_missing、SmalltalkのDNUや他の似たような言語機能に似た、noSuchMethod機能でいくつかの動的マジックを可能にしている。将来バージョンのJavascriptはまた、徐々に、V8のような現在のJavascript VMへの道を作っており、フォームでDynamicプロキシという同じような機能をサポートする。

閉じたクラス、Evalなし

Rubyのような言語は、オープンクラスと呼ばれる、実行時にクラスの変更ができる。この機能がないことはパフォーマンスの助けになる:すべてのメンバーは、コンパイル時に認識され、コードの分析と参照されない機能の削除を可能にする。 以下の'批判'セクションを参照して、現在のステータスと、他の言語に存在している現在のソリューションを見て欲しい。

将来の言語機能

async/awaitスタイル拡張は、I/Oコードを容易にするために検討されている。DartのI/O APIの多くは非同期で、容易に受け入れられるようにサポートされている。Coroutines、Fibersとそれらの変異のような機能を追加しなかった理由は、同期機能の追加を避けるためである。スケジュールとインターリーブが可能なシステム内のCoroutinesは、それらの実行と正しいコードを書くために、同期の共有リソースが必要だ。したがって、シングルスレッドにフォーカスすると、並行処理は、Isolateで完結しており、: 明示的なコミュニケーション、共有無し、Isolateはロックしないなど。

批判

開発者が新しいプログラム言語にイライラすることはない。いくつかの批判をざっと見てみよう。

DartCがDartを巨大なJavascriptファイルにコンパイルする

リンクは、Dartの"Hello World"アプリケーションが数千行のJavascriptコードにコンパイルされることを示している。 端的な答えは:ツリーシェイキングのような最適化を加える、すなわち未使用の関数の削除であるが、これらはGoogle DartとDartC チームのToDoリストに上がっている。

Dartの特徴としてこれらの最適化は可能であり、クラス内で、コンパイル時にすべての関数がわかっている場合は、特にやりやすい。 evalの欠如は、すべてがコンパイル時であることを意味し、コンパイラはどの関数が使われているかを知っていて、さらに重要なのは:なにが使われていないかを知っていることだ。後者は、アウトプットから安全に削除することができる。

GoogleのClosureのユーザーは、これを高度なコンパイル(Advanced Compilation機能として知ることになる。Closureは、Javascriptにクラスシステムをもたらし、情報を付加したアノテートクラスを可能にした。アドバンストモードでは、Closureコンパイラがは開発者が一定のルールに準拠しているとみなし、コードの中で関数が明示的に参照されていない場合、それを破棄することができると想定する。もし、開発者がevalや他の機能を使ってルールを破ったとしたら、コードが破損してしまう。

Google Dartはルールを守るのにプログラマを信頼する必要がないのは、言語の制約がコンパイラに必要な保証を提供するためである。

Google Closureの高度なコンパイルを使ったもうひとつの言語の例は、ClojureScript (Clojureに'j'を追加した)である。 ClojureScriptはまた、アプリケーション言語であることを意味し、evalや他の動的な言語読み込み機能が欠けている。Google Closureの高度なコンパイルツールでJavascriptにコンパイルされると、コンパイラは、使われてないライブラリの関数を削除することができる。

なぜ、実行時最適化のために静的な型付けを使用しないのか?

なぜ型付けが任意で、なぜそれが生成されたコードを改善するために使われていないのか。きっと、intのようになにかを知っていることは、生成されたコードの最適化を助ける必要がある。
結局のところ、彼らは過去にVMや2つを過去に行ってきており、Dartの後方のチームはこのアイディアについて知っている。Google V8とOracleのHotSpot がまさにその2つの例である。

Dartの静的な型情報は、いくつかの理由で実行時コードの役には立たない。ひとつは: 開発者が特定した型は、意味的にまったくインパクトがなく、実際のところ彼らは完全に間違えたことができる。このようなケースでも、型チェッカーから警告を受けるだろうが、プログラムはきちんと動作する。 さらに、与えられた型を無視することができるため、それらが信頼できないため、VMは最適化に使用することができないintであると書くことは、実行時にオブジェクトが本当はStringであり間違っていることを開発者が明示するためだけである。
静的型システムは、ツールとドキュメントの助けになるが、実行されたコードではなにもしない。

静的な型が最適なコードを生成する助けにならない他の理由は: Dartがインターフェイスベースであるためである。オペレータがintというのは、インターフェイスのメソッドを呼び出す実際のメソッド呼び出しである。Dartは本気で、たとえばintはクラスではなく、実際にインターフェイスである。
インターフェイスメソッドを呼び出すことは、実際のオブジェクトとそのクラスをベースとして、実行時にそれを解決することを意味する。コールサイトのインラインキャッシュをする(polymorphic)のようなコンセプトは、メソッドを探すオーバーヘッドを削減する助けができる。 StrongTalkとその直系であるHotSpotは、どのコードが実行に実行されて、最適化されたコードを生成するかを算出する、フィードバックベースの最適化を行う。V8はまた、Crankshaftの方式で、最近これらの最適化を行っている。

私のお気に入りの言語機能はどこか?

Googleはより早い段階のDartをリリースした。これは、言語仕様、IDE、VM、DartCなどによって騙されやすい: Dartチームからの明確なメッセージは: 今は、Dartを試してフィードバックを提供する時期である。多くの機能はすでに計画されているが、終了していないか、まだ実装されていない。リフレクションとMixinは、いくつかのアイディアはすでに将来的な潜在機能として言及されている。

もし機能がDartリポジトリや言語仕様にない場合、が、フィードバックを提供して、修正の議論や言語やランタイム環境の修正を議論するときだ。

まとめ

Dartの多くの作業が完了した:言語仕様、Dart VM、DartをプレーンJavascriptにコンパイルするDartCコンパイラ、SWTをベースとして、いくつかのEclipseにバンドルされたエディタなど。

しかしながら、初期リリースのDartは技術プレビューであり、言語、APIそしてツールは非常に多く、進行中の作業である。今は、Dartチームにフィードバックを提供して、実際に言語に影響を与えるチャンスがあるときだ。言語は、いくつかの提案と計画された変更がこの資料に言及されており、変更される予定だ。

いくつかはすでにDartの検証をはじめており、Java 7のinvokedynamic機能を多用したJavaへの移植はJDartプロジェクトで始まっている。

この記事で私たちは言語とDart VMの機能にフォーカスしたが、DartCはDartコードをプレーンJavascriptにコンパイル可能だ。紹介ページで提示されているいくつかのサンプルには、iPadで実行されるものを含めて標準ブラウザで実行される、実際にJavascriptにコンパイルされたDartアプリケーションが含まれている。

Google Dartの初期開発は、秘密裏に行われていたが、ソース、ツール、チケットシステムなどプロジェクト全体は、現在オープンになっている。Dartを採用するのであれば、見ておくとよい。 前述のようにDart VMは、クライアント開発者と同じようにサーバー開発者の両方にアピールする機能が提供される。

リンク:

 著者について

Werner Schuster (murphee) ときどき、ソフトウェアを書いて、ときどき、ソフトウェアについて書く。 

 

 

この記事に星をつける

おすすめ度
スタイル

BT