第4章 練習問題 3-19
3 並列フィボナッチ
{Fib 30}同士を比較した。単位はミリ秒。並列のほうが遅い。まあ、シングルプロセッサですし。
local S = {Property.get 'time.total'} in {PFib 30 _} {Show '---'} {Show {Property.get 'time.total'}-S} end
- 直列 - 410
- 並列 - 4980
また、Fib(n)が生成するスレッドの数をT(n)とすると、
T(1) = 1 T(2) = 2 T(n) = 1+T(n-1)+T(n-2)
ここで、S(n)=T(n)+1とすると、
T(n) = 1+T(n-1)+T(n-2) => T(n)+1 = (T(n-1)+1)+(T(n-2)+1) => S(n) = S(n-1) + S(n-2)
するとS(n)はフィボナッチ数になるので、
T(n) = Fib(n)-1
となる。Fib(n)はO(2^n)であるので、生成されるスレッドはO(2^n)となる。
id:selvaggioに教えてもらった。
4 順序一定並列性
declare A B C D in thread D=C+1 end % (1) thread C=B+1 end % (2) thread A=1 end % (3) thread B=A+1 end % (4) {Browse D}
- スレッドの起動は上から順番に行なわれる: 1->2->3->4
- 加算はデータフロー変数によって制限される: 3->4->2->1
- 結果は4
- 直列の場合は、上から順番に。結果は4
5 Wait操作
- ifは条件部が決定状態ない場合は、決定するまで待機する
- A==Bは、AとBの等・不等が分かるまでブロックする
そのため、この操作はXが決定されるまでブロックするため、Waitとして用いることができる。
6 スレッドのスケジューリング
fun {Skip1 Xs} case Xs of X|Xr then if {IsDet Xr} then {Skip1 Xr} else Xs end [] nil then nil end end fun {Sum Xs A} case {Skip1 Xs} of X|Xr then {Sum Xr A+X} [] nil then A end end
Generaterの走るスレッドをG、Sumの走るスレッドをSと省略する。
- G: リストを1|2|3|Xに束縛する
- S: 1|2|3をSkipする。Xを待つ
- G: X=4|5|6|Y
- S: A=0+4。そして5|6をSkipし、Yを待つ
- G: Y=7|8|9|Z
- S: A=4+7。そして、8|9をSkipし、Zを待つ
を繰り返すため、合計が本来よりも少なくなる。
7 高階プログラミンングを使うプログラムされたトリガ
fun {DGenerate N} fun {$} N#{DGenerate N+1} end end fun {DSum F A Limit} if Limit > 0 then X#G = {F} in {DSum G A+X Limit-1} else A end end local F S in thread F = {DGenerate 0} end thread S = {DSum F 0 150000} end {Browse S} end
8 並列状況におけるデータフロー的振る舞い
(a)
ブロックする。Aが未束縛のため、caseで待機状態に入るため。
(b)
表示される可能性があるのは、以下の2通り。
- _: Filterが一度も実行される前にShowが実行された場合。
- 5|_: Filterが一度以上、実行された後にShowが実行された場合。
また、変数は常に一通りの値に束縛されるため非決定性を持たない。
(c)
少なくとも、自分の環境では5|_が表示された。_が表示されないとは言いきれない。
Delay中に他のスレッドが走っている。
(d)
"[5 6 4]"が表示される。A=6によりFilter内のcaseが待機状態から脱けるため。
9 ディジタル論理シミュレーション
nビット数を以下のように表現することにする。
[x0 x1 x2 ..] % e.g. 0x001 => [1 0 0]
するとnビットの加算器は以下のようになる。
fun {NAdder Xs Ys} C in {FullAdder Xs Ys 0|C C} end % 0b101 + 0b001 = 0b110 {Browse {NAdder 1|0|1|_ 1|0|0|_}} % 0b001 + 0b001 = 0b010 {Browse {NAdder 1|0|0|_ 1|0|0|_}} % 0b100 + 0b100 = 0b000 {Browse {NAdder 0|0|1|_ 0|0|1|_}}
10 遅延の基礎
次のような呼び出すたびに、値の代る式を書くことができるので。(宣言的でない場合、問題が生じるので)
fun lazy {LazyTime} {Time.time} end
11 遅延性と並列性
振る舞いの説明
- (X+Y)+Zが実行される場合は、まずXとYが必要とされる。なので、x,yが即座に表示される。
- XとYが束縛されるのは6秒後なので、その後Zが必要とされる。なので、zが6秒後に表示される
- Zが束縛されるのがさらに9秒後なので、15が表示される
X+(Y+Z)の場合
- 即座にy,zが表示される
- 9秒後にxが表示される
- 12秒後に6が表示される
theard X+Y end + Zの場合
- 即座にx,y,zが表示される
- 9秒後に6が表示される
速いプログラム
thread X+Y end +Zのように全てthreadで包むと、すべての変数が同時に必要となる。なので、これが最速となる。
(thdead ... (thread (thread (thread I1+I2 end) + I3 end) + I4 end) + ... ) + In
12 遅延性と漸増性
並列を使うプログラムは、Generateが先行して入力ストリームを生成する。
一方、遅延を使うプログラムは、Sumが必要として部分だけをGenerateが生成する。
また遅延と並列を一緒に使っても、主に遅延によって制御されているので大差はない。
fun lazy {Generate N} N|{Generate N+1} end fun {Sum Xs A Limit} if Limit>0 then case Xs of X|Xr then {Sum Xr A+X Limit-1} end else A end end local Xs S in thread Xs={Generate 0} end thread S={Sum Xs 0 60} end {Browse S} end
13 遅延性と一枚岩関数
振る舞いは同じ。Revでリストを最後までたどるので、遅延の意味はない。
遅延になった分効率が悪いはずなので、Reverse2のほうが効率が悪い。
14 遅延性と反復性
ByNeedを使って書き直すと次のようになる。反復的に見えるが、違うのかなぁ?
proc {LAppend As Bs ?R} {ByNeed fun {$} case As of nil then Bs [] A|Ar then X Xr R = X|Xr in X=A {LAppend Ar Bs R} end end} end
15 遅延性の性能
% 性急版 fun {Generate N Limit} if N =< Limit then N|{Generate N+1 Limit} else nil end end fun {Sum Xs} case Xs of X|Xr then X+{Sum Xr} [] nil then 0 end end % 遅延版 fun lazy {Add X Y} X+Y end fun lazy {LSum Xs} case Xs of X|Xr then {Add X {LSum Xr}} [] nil then 0 end end fun lazy {LGenerate N Limit} if N =< Limit then N|{LGenerate N+1 Limit} else nil end end % 速度の測定 fun {Bench F} Now = {Property.get 'time.total'} in {F} {Property.get 'time.total'}-Now end {Browse {Bench proc{$} {Browse {Sum {Generate 0 100000}}} end}} {Browse {Bench proc {$} {Browse {LSum {LGenerate 0 100000}}+0} end}}
16 by-need実行
待機状態に入らず、値を要求する処理が思いつかなかった。そこで、別スレッドで待機するようにした。
proc {Need X} thread {Wait X} end end
17 ハミング問題
% 素数生成のための関数群 fun lazy {LFilter L F} case L of nil then nil [] X|L2 then if {F X} then X|{LFilter L2 F} else {LFilter L2 F} end end end fun lazy {Generate N} N|{Generate N+1} end fun lazy {Sieve Xs} case Xs of X|Xr then X|{Sieve {LFilter Xs fun {$ Y} Y mod X \= 0 end}} end end % 4.5.6より。元の問題で使った関数群 fun lazy {Times N Xs} case Xs of X|Xr then N*X|{Times N Xr} end end fun lazy {Merge Xs Ys} case Xs#Ys of (X|Xr)#(Y|Yr) then if X<Y then X|{Merge Xr Ys} elseif X>Y then Y|{Merge Xs Yr} else X|{Merge Xr Yr} end end end % 本題 fun {Hamming K} Prime = {List.take {Sieve {Generate 2}} K} H X Xr in X|Xr = {Map Prime fun {$ P} {Times P H} end} H = 1|{FoldL Xr Merge X} H end % テスト proc {Touch N H} if N>0 then {Touch N-1 H.2} else skip end end H = {Hamming 5} {Touch 20 H} {Browse H}
18 並列性と例外
static analysis errorによりコンパイルできない:D。というのは冗談として、以下のように番号を振る。
local U=1 V=2 in try thread try U=V % (1) finally {Browse bing} %(2) end end finally {Browse bong} %(3) end end
そして、1と2の間には前後関係が存在しているので、
- bong->例外発生->bing->例外を再raise
- 例外発生->bong->bing->例外を再raise
- 例外発生->bing->bong->例外を再raise
- 例外発生->bing->例外を再raise->bong
という順序が存在する。
19 宣言的並列性の限界
Megreは3つのストリームを一つづつ読むという風に順番が決定的であった。しかしサーバの場合は先に到達したものから読むという非決定性が必要となるため宣言的モデルでは実現できない。