For Development HEAD DRAFTSearch (procedure/syntax/module):

12.10 control.pmap - 並列map

Module: control.pmap

このモジュールは、コードを複数スレッドで並列に走らせる高レベルユーティリティを提供します。 例えばpmapは、mapと同様に、 与えられたコレクションの各要素に手続きを適用してその結果をリストにして返しますが、 手続きの適用は並列に行われます。

応用によって望ましい並列化戦略が違うので、仕事の割り振りの戦略をカプセル化する mapperオブジェクトも提供されます。

高レベルAPI

Function: pmap proc collection :key mapper

{control.pmap} procはひとつの引数を取る手続き、collectionはコレクションです (gauche.collection - コレクションフレームワーク参照)。

collectionの各要素に、procを適用します。 適用は複数のスレッドを使って並行して行われるかもしれません。結果はリストにまとめられて返されます。

mapperキーワード引数にmapperを渡すことで、各スレッドにどのように仕事が 割り振られるかをカスタマイズできます。

Function: pfind pred collection :key mapper
Function: pany pred collection :key mapper

{control.pmap} これらは、predを満たす要素を一つ見つけたい時に使えます。 条件に合う要素が見つかった時点で、他のタスクはキャンセルされます。

pfindfindのように、predを満たす要素を見つけたらその要素自身を 返します。一方、panyanyと同じように、 pred#fでない値を返したらその返り値を返します。

predを満たす要素が一つも無ければ#fが返ります。

predを満たす要素が複数あった場合にどれが返るかは様々な状況に依存するので、 決定的な振る舞いを期待すべきではありません。

Mappers

mapperはタスクを並列に走らせる戦略をカプセル化したものです。 次のmapperが提供されます。

Static mapper

いくつかのスレッドを作り、タスクを均等に割り振ります。 タスク数が多く、各タスクにかかる時間がそれほど分散しない場合に適しています。 他のマルチスレッドmapperよりもオーバヘッドが少ないです。

Pool mapper

スレッドプールを使ってタスクを処理します。タスク数が多いか、 各タスクの実行にかかる時間の分散が大きい場合に適しています。 また、スレッドプールを再利用することもでき、スレッド作成のオーバヘッドを削減できます。

Fully concurrent mapper

タスクの数だけスレッドを作成して実行します。タスク数がそれほど多くなく、 タスクの中でブロッキングI/Oをする場合に適しています。

Sequential mapper

これは呼び出したスレッド内でタスクを逐次的に実行します。並行実行は行われません。 これには2つの目的があります。(1)シングルコアのシステムでは、 これが最もオーバヘッドの少ない戦略です。(2)並行性による複雑さを除外してアルゴリズムの 動作を確かめるのに使えます。 シングルコアシステムでは、これがdefault-mapperの初期値です。

Parameter: default-mapper

{control.pmap} pmap等が使うmapperの省略時の値を保持するパラメータです。

Gaucheが複数コアのシステム上で走っている場合はコア数と同じスレッドを使う static mapperが、そうでなければsequential mapperが初期値となります。

このパラメータにセットされたmapperは再利用されたり、複数のpmap呼び出しから 同時に使われたりする可能性があります。 外部スレッドプールを使うpool mapperはスレッドプールを共有するので、 それをデフォルトのmapperに設定する時は注意してください。

Function: sequential-mapper

{control.pmap} sequential mapperのシングルトンインスタンスを返します。

Function: make-static-mapper :optional num-threads

{control.pmap} static mapperの新しいインスタンスを作って返します。 このmapperはpmapの実行のたびにnum-threadsのスレッドを作成し、 必要な仕事を均等に割り振ります。小さく均等なタスクを大量にこなす場合に便利です。

num-threadsが省略された場合は、sys-available-processors が返すプロセッサ数を使います (環境の問い合わせ参照)。

Function: make-pool-mapper :optional external-pool

{control.pmap} スレッドプールを使うpool mapperの新たなインスタンスを作って返します。 タスクごとに計算負荷が大きく変動する場合に適しています。

external-poolが指定されなければ、mapperは高レベルのマッピング操作が呼ばれる 度に新たなスレッドプールを作り、マッピング操作が終わったらプールをシャットダウンします。 この場合、ひとつのスレッドプールは一回のpmapなどの呼び出しだけに使われ、 他と共有されることはありません。

あるいは、既にあるスレッドプールをexternal-pool引数に渡すこともできます。 その場合、このmapperインスタンスが使われる場面全てで、そのスレッドプールは再利用されます。 外部スレッドプールを使うことで、pmapを呼ぶ度にスレッドプールを作ったり シャットダウンしたりするオーバヘッドを避けることができます。 しかし、次の点に注意しなければなりません。

  • mapperをもう使わなくなってからスレッドプールをシャットダウンするのは、 ユーザの責任です。
  • mapperは渡されたスレッドプールをpmapの呼び出しに使いまわすので、 ひとつのpool mapperが複数のpmap呼び出しで同時に使われることがないように 管理してやる必要があります。特に、デフォルトのmappersとして外部スレッドプールを 使うpool mapperを設定するのは危険です。
Function: make-fully-concurrent-mapper :optional timeout timeout-val

{control.pmap} タスクの数だけスレッドを作成し、全てを並行に評価する完全並行mapperを作って返します。 タスクの数が比較的少数で、各タスクがI/Oなどでブロックする場合に適しています。 スレッドを作成するオーバヘッドが比較的大きいですが、スレッドがI/O待ちの間に CPUを有効活用できます。

省略可能なtimeout引数で、スレッドにタイムアウトを指定できます。 thread-join!のtimeout引数と同様に、<time>オブジェクトによる 絶対時刻、実数による現在時刻からの相対秒、そしてタイムアウトを設けない#fが 指定できます。省略時はタイムアウトしません。

タイムアウトした場合、timeout-valprocの結果の代わりに使われます。 timeout-valの省略時値は#fです。

註: スレッドがタイムアウトした場合、 走行中のスレッドはthread-terminate!で終了させられます。 この時スレッドがmutexをロックしていればそれはabandoned状態になりますし、 リソースを掴んでいたら適切なクリーンアップがなされないかもしれません。 スレッドの実行を制限時間内に納め、かつクリーンアップを確実にしたい場合は、 そういうロジックを明示的にprocに組み込んでください。



For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT