Parallel Schemeに向けたテストその3
イプシロンに並列処理を導入する目的はREPLからGLUTやGTKなどを起動してこれらを非同期(*1)に動かすことです。そこでspawnも実装してテストしてみました。
(*1) parallel mapは呼び出したスレッドを結果が揃うまでブロックするのでこの用途には使えないのです。
REPLからGLUTのスレッドとGTKのスレッドを起動しています。ここでGTKのボタンを押すとGLUTの表示が変化します。またコンカレントGCを3つ並列に動作させることにより、例えばREPLで重い処理を開始したとしてもGLUTのフレームレートに影響が出ないようにしています。
注:現在のspawnはまだ限定された条件でしか動きません。簡単にSEGVするのでtrunkでもまだサポートしていません。
ちなみにparallel mapはspawnを使って実装することもできますので簡単に作ってみました。
(import (concurrent) (tidbits dotimes)) (define n-way-parallel-map (lambda (n proc args) (let ((in (make-shared-queue)) (out (make-shared-queue))) (define (pmap-worker) (let loop ((arg (shared-queue-pop! in))) (unless (eof-object? arg) (shared-queue-push! out (proc arg)) (loop (shared-queue-pop! in))))) (define (pmap-pump) (for-each (lambda (n) (shared-queue-push! in n)) args) (dotimes n (shared-queue-push! in (eof-object)))) (dotimes n (spawn pmap-worker)) (spawn pmap-pump) (map (lambda (n) (shared-queue-pop! out)) args))))
ワーカースレッドの数を8個から1個まで変化させてn-way-parallel-mapを使ってみました(n-cpstakは「その2」で使ったものと同じです)
(time (n-way-parallel-map 8 n-cpstak '(100 100 100 100 100 100 100 100))) ;; 3.501519 real 6.916432 user 0.064004 sys (time (n-way-parallel-map 4 n-cpstak '(100 100 100 100 100 100 100 100))) ;; 3.501702 real 6.904432 user 0.056004 sys (time (n-way-parallel-map 2 n-cpstak '(100 100 100 100 100 100 100 100))) ;; 3.360874 real 6.648415 user 0.036003 sys (time (n-way-parallel-map 1 n-cpstak '(100 100 100 100 100 100 100 100))) ;; 5.836247 real 6.692418 user 0.016001 sys
2 CoreのCPUなのでワーカーが2個を越えるとオーバーヘッドのみが増えていることが分かります。16 Coreなどでどのくらいスケールするのか興味のある所ですが・・・マシンもってないので調べられません :(
ところでシングルスレッドでの実行速度は次のようになっています。
(time (map n-cpstak '(100 100 100 100 100 100 100 100))) ;; 6.310413 real 10.40065 user 0.040002 sys
ここで注目すべき点はワーカーが1個のn-way-parallel-mapでもシングルスレッドのmapよりも性能が良くなっている(*2)ということです。これならspawnだけをプリミティブにして後はSchemeで構築するという方法で行けそうです。並列処理のプリミティブは開発にかなり気を使うので少なく済ませたいのです :p
(*2) 「その2」にも書きましたが、これはGCの負荷が下がっているからと考えています。