Ecma-408 2nd revision
DartはEcma-408という名前で仕様が公開されていますが、
最近、Ecma-408の2nd revisionができたようです。
公式ブログでも宣伝されてます。
http://news.dartlang.org/2014/12/enums-and-async-primitives-in-dart.html
2nd revisionで追加されたのは、以下の3機能になります。
- Enumerations (enum)
- Asynchrony (async, await, and more)
- Deferred loading (import ... deferred as)
enumはDart 1.9で実装済み
async-awaitは、1.8でphase 1まで実装済み
deferred loadingは、1.6で実装済み
dartのasync-awaitの実装状況とか、私が過去にまとめた資料は以下です。
http://nothingcosmos.blog52.fc2.com/blog-entry-180.html
https://dl.dropboxusercontent.com/u/58430726/DartVM/src/2014dart.html
async-awaitのサンプル
async-awaitは、公式ページで使い方が説明されてました。
https://www.dartlang.org/articles/await-async/
それとは別に、挙動を確認しつつ、
自分なりに多少実用的かな?というサンプルを作ってみました。こちらです。。
import 'dart:async';
int fibo_async(int n) async {
if (n < 2) {
return n;
} else {
return await fibo_async(n-1) + await fibo_async(n-2);
}
}
int count = 0;
int fibo(int n) {
count++;
if (n<2) {
return n;
} else {
return fibo(n-1) + fibo(n-2);
}
}
loop() async {
for(;;) {
await new Future.delayed(const Duration(seconds: 1), () => print("loop"));
}
}
calcSync(int n) async {
count = 0;
var watch = new Stopwatch()..start();
var ret = fibo(n);
print("elapsed_ms=${watch.elapsedMilliseconds}, fibo($n)=${ret}, count=$count");
}
calc(int n) async {
var watch = new Stopwatch()..start();
fibo_async(n).then((p) => print("elapsed_ms=${watch.elapsedMilliseconds}, fibo($n)=$p"));
}
main() {
loop();
// calcSync(42);
// calcSync(28);
// calcSync(29);
// calcSync(30);
// calcSync(31);
// calc(28);
// calc(29);
calc(30);
// calc(31);
}
何をしているかというと、毎度お馴染みのfibo()関数をasync-awaitで修飾してみたのが、fibo_async()関数です。
fibo_async()が実行されるのと並行して、loop()で1秒ごとに割り込んでいます。
loop()で1秒ごとに割り込むのと並行して、fibo_async()で少しずつ計算をすすめ、約10秒後に計算結果を返しています。
実行結果は私の環境ではこんな感じです。
//calcSync()のコメントを外せば、最初にfibo()を計算するため、loop()の割り込みを数秒間ブロックすることになります。
$ dart --enable-async async_fibo.dart
loop
loop
loop
loop
loop
loop
loop
loop
loop
elapsed_ms=9347, fibo(30)=832040
loop
loop
loop
メリット
async-awaitを使用すれば、fibo()のようにCPUを食ってビジーになりやすい処理を適度に分割することができます。
結果として、ビジーのまま応答を返さない挙動を改善することができます。
なんでこんな面倒な方法になるのかというと、DartはThreadをサポートしておらず、
ただ1つのmain threadが、EventLoopしながら処理しています。
Dartのシングルスレッド実行に関しては、公式サイトを翻訳された下記の記事が大変わかりやすいです。
Dart Advent Calendar 2014 の 14日目の記事です。
http://qiita.com/takyam/items/6ad155678c95bba4047f
上記から引用しますが、
タスクをスケジュールする場合は、いくつかのルールに従ってください:
...
アプリが応答可能な状態を保つために、イベントループ上での数値計算タスクを避けてください
数値計算タスクを行う場合、isolateかworkerを追加で作成してください。
上記の数値計算タスクが、fibo()に該当しますね。
本来であれば、こういった数値計算処理専用のIsolateを別途立てて、
そこにタスクを投げ結果をfutureで待つのが一般的だと思います。
Isolateを新規作成すると、1つスレッドを作成し、その上で稼働するVMもどき
(Heap,GC,JITコンパイラ,Coreやクラスの読み込み結果などが、Isolateごとに新規作成)
を作成します。これはDart起動時にIsolateを1つ作成するのとまったく同じです。
そのため、Multithreadingのような挙動を期待する場合、Isolateを複数作成し、メッセージでやりとりする必要があります。
デメリット
デメリットとしては、シングルスレッドのEventLoopを回りながら、
awaitのcontext間でcontext switchのようなことを行うため、オーバーヘッドが発生します。
上記のサンプルでは、fibo(30)を実行していますが、通常では10ms程度の処理が、
async-awaitで修飾すると10,000 ms程度かかります。
fibo(30)では、2,692,537回ほど再帰呼び出しされるようですが、
その中でawaitは2個挿入しています。
await 1個につき、context switchに2microsecond程度のオーバーヘッドと考えてよさそうです。
※他にもオーバーヘッド等あるのかもしれませんが、現状の感覚として。。