14. 並列性、並行性、非同期性

14.1. 並列性

一般的な状況では、プログラム内でのタスクは、順次実行されます。
しようとしていることにかなりの時間がかかる場合、これが問題になることがあります。

簡単に言って、Perl6には物事を並列に実行する機能があります。
ここで、「並列性」には2種類の違った意味があることに注意しましょう:

  • タスク並列性: 2つ(あるいはそれ以上)の独立した式が並列に実行されること

  • データ並列性: 1つの式がリストの要素ひとつひとつに並列に適用されること

2番目の例から先に見てみましょう。

14.1.1. データ並列性

my @array = (0..50000);                     # 配列の作成
my @result = @array.map({ is-prime $_ });   # 配列の各要素に対して is-prime を呼ぶ
say now - INIT now;                         # スクリプトの実行にかかった時間を出力する
上の例を考えてみましょう

上でしている操作は一つだけです @array.map({ is-prime $_ })
is-prime サブルーチンが配列の各要素に対して、順次呼ばれています:
まず is-prime @array[0] 次に is-prime @array[1] 次に is-prime @array[2] と続きます。

幸運なことに、 is-prime を配列の複数要素に対して同時に呼ぶことができます:
my @array = (0..50000);                         # 配列の作成
my @result = @array.race.map({ is-prime $_ });  # 配列の各要素に対して is-prime を呼ぶ
say now - INIT now;                             # スクリプトの実行にかかった時間を出力する

式の中で race が使われていることに注意してください。 このメソッドを使うことで、配列の各要素に対する並列実行をすることができます。

両方の例(raceありとなし)を実行して、スクリプト終了までの時間を比べてみてください。

race は、配列要素の順序を保ちません。もし、順序を保ちたいなら、代わりに hyper を使ってください。

race
my @array = (1..1000);
my @result = @array.race.map( {$_ + 1} );
@result>>.say;
hyper
my @array = (1..1000);
my @result = @array.hyper.map( {$_ + 1} );
@result>>.say;

両方の例を実行すると、片方では数字の順に出力され、もう片方ではバラバラに出力されます。

14.1.2. タスク並列性

my @array1 = (0..49999);
my @array2 = (2..50001);

my @result1 = @array1.map( {is-prime($_ + 1)} );
my @result2 = @array2.map( {is-prime($_ - 1)} );

say @result1 == @result2;

say now - INIT now;
上の例を考えてみましょう:
  1. 2つの配列を定義します

  2. それぞれの配列に対して別の操作をして、その結果を保存します

  3. それから、二つの配列が同じであることをチェックします

このスクリプトでは、 @array1.map( {is-prime($_ + 1)} ) が終わるのを待って、
それから @array2.map( {is-prime($_ - 1)} ) を評価しています。

それぞれの配列に対する操作は、お互いに影響しません。

それなら並列にしてみましょう
my @array1 = (0..49999);
my @array2 = (2..50001);

my $promise1 = start @array1.map( {$_ + 1} );
my $promise2 = start @array2.map( {$_ - 1} );

my @result1 = await $promise1;
my @result2 = await $promise2;

say @result1 == @result2;

say now - INIT now;
解説

start メソッドはコードを評価して、 プロミス(約束)型のオブジェクト 略して プロミス(約束) を返します。
コードが問題なく評価されれば、その プロミス守られ ます。
コードが例外を投げれば、その プロミス破られ ます。

await メソッドは プロミス を待ちます。
もしプロミスが 守られ れば、帰ってきた値を得ます。
もしプロミスが 破られ れば、投げられた例外を得ます。

それぞれのスクリプトの実行にかかった時間をチェックしてみてください。

並列化には、必ずスレッド化のオーバーヘッドがあります。もし計算速度の向上よりもオーバーヘッドの方が大きい場合には、そのスクリプトは遅くなるでしょう。
これが、 racehyperstartawait を比較的単純なスクリプトで使うと、スクリプトが実際には遅くなってしまう理由です。

14.2. 並行性、非同期性

並行プログラミング、非同期プログラミングについて詳しくは、 http://doc.perl6.org/language/concurrency(英語)を参照してください。