テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

「Erlang入門」という本を読んで, ちょっと使ってみた.

ストレージ部からビッグデータ部に電撃異動(or 放出. いや, 栄転だ!!!)となり, 今後, 以前とは違ったスキルが要求されることは何か. やっぱ並列処理じゃないの?直感的にはそんな気がします. おれには虎の子のGPUプログラミングがあるぜ!でもこれは出来ない! GPUをやりたいと言ったら, ページ数10000枚の提案書と, 長さ10000のスタプラリーが必要となる. GPUを自製すると言い出す可能性もある... GPUは使えない. よかろう, ならばメニーコアだ!引越し準備も終わり「暇〜」になってしまったので本棚にあった「Erlang入門」という本を読んでみました.

プログラミング言語Erlang入門

プログラミング言語Erlang入門

内容としてはErlangの初歩を説明していて, 3時間くらいで読めました. でも, たぶんそれは僕にもともと関数型の(浅い)経験があるからであって, 全くの初心者が読むときついかも知れない. funとかいきなり出てきたけど何?匿名って何?ポカーンかも. おれには, thrustライブラリを使い倒した経験や, Javaでのメッセージパッシングプログラミングの経験があるので, ここらへんも理解を助けたと思います. 著者としても, 全くのプログラミング初心者を前提としているわけではないと思う箇所がありました. Erlangの本なんて手にとる意識の高いプログラマにとっては問題なしという判断か.

Erlangは, 並列処理をするための言語です. spawnという関数によってErlang独自のメモリ独立したプロセスを生成します. 他のプロセスに対して働きかけるには, spawnから返ってきたPidに対してPid ! Argsという構文でメッセージを送ります. メッセージを受け取る側では, receiveを定義して, 受け取ったメッセージに対する解析とそれに応じた処理を行います. Pidという概念は, (たぶんですが) Nodeという概念に昇格されて, 分散環境にも拡張される. Nodeというのは軽量な仮想マシンだと思います. 結果として, サーバ内部の処理と, サーバの外の世界の処理を透過的に統合することが出来るとかその手の話だと思います(たぶん). これは, 京とか, そういう世界でも研究されていることだったと思いますし, そういうのをエレガントにやっちゃうのね, みたいな印象を持ちました.

演習

帰宅してから早速, 演習をしてみました(うわっ・・・私の意識, 高すぎ・・・?). 設定した問題は「N人の生徒がいます. そいつらがテストを受けて0から99点の得点をとりました. これから0-9点, 10-11, ... の分布を作成する」という, 良くあるものです.

コードの概要は以下です.
1. (map) N並列で, 得点 -> 自分のいるレンジの計算を行う. そして, reduceノードにメッセージを行う.
2. (reduce) 10並列で待機していて, メッセージを受け取ると, incしてまた待機する.

receiveで待っている人に対してメッセージが2つ同時に来たらどうなるんだろうという疑問は, Erlangが解消してくれているようです. キューを持っていて, メッセージはシリアライズされている???

実際にコーディングすると以下のようになりました. 構文が分からないので, ネットで色々見ながら適当にやったので正しいかどうかは知りませんが, なんとなくちゃんと動いているような?

一番ハマったことは, http://erlang.2086793.n4.nabble.com/escript-vs-erl-td3321834.html で解決されました. なぜ解決されたかは分からないけど, -mode(compile)をつけないとダメとか, 良く分かりませんが動いたのでよし!あと, spawnに食わせているdo_reduceとdo_mapはexportする必要があるようです?そうしないと動きませんでした.

-module(scorecount).
-mode(compile).
-export( [main/1, do_reduce/3, do_map/1] ).

%% [X, X+1, X+2, ...]
create_list(_, 0, L) ->
        L;
create_list(X, N, L) ->
        create_list(X+1, N-1, [X | L]).

%% [Int]
%% 0 <= Each element < 100
%% Nobody is perfect.
generate_sample_scores() ->
        [ random:uniform(99) || E <- create_list(0, 1000000, ) ].

do_reduce(K, N, L) ->
        receive
                { X } ->
                        LL = [X|L],
                        io:format("group(~p): count(~p), list(~p) ~n", [K, N+1, LL]),
                        do_reduce(K, N+1, LL)
        end.

mod_spawn(Fun, Args) ->
        spawn(?MODULE, Fun, Args).

reducer_name(K) ->
        list_to_atom("r" ++ integer_to_list(K)).

reg_reducer(K) ->
        PID = mod_spawn(do_reduce, [K, 0, ]),
        register(reducer_name(K), PID).

reduce() ->
        ;
reduce([K|REST]) ->
        [reg_reducer(K) | reduce(REST)].

calc_key(X) ->
        X rem 10.

get_reducer(K) ->
        reducer_name(K).

do_map(X) ->
        K = calc_key(X),
        get_reducer(K) ! { X }.

%% Int -> [(Int, Int)]
map() ->
        ;
map([H|T]) ->
        [mod_spawn(do_map, [H]) | map(T)].

main(Args) ->
        Scores = generate_sample_scores(),
        io:format("Scores ~p ~n", [Scores]),
        Reducers = reduce(create_list(0, 10, [])),
        io:format("Reducers ~p ~n", [Reducers]),
        Mappers = map(Scores),
        io:format("Mappers ~p ~n", [Mappers]).

現状では, 最終的にちゃんと集計してまとめて表示するということはしてません. したいんだけど, やり方がよく分からなかった. spawnに登録された関数自体どのタイミングで実行されているのか分からないし, どこかで同期がとられているのかも分からない. まるで魔法ですね.

結果

仮想CPU2コアの仮想マシン上で上のコードを動かしてsarを見ると, 2つのCPUの片方しかまともに動いていないことが分かりました. 別のマシンでやって, まともな結果が出たら書くことにします.

core i7 3770で実験しました. これは, 実行時のある瞬間におけるtopからCPUのusageを切り出したものです. mapする人の数を増やすと, なぜか処理がシリアライズされるという現象があり, この結果は1Mスレッドの時です. 当然, 処理自体はほぼ一瞬で終わってしまうので, 次の瞬間にはCPU利用率は0%になります. 一つ一つのmap処理が重くしないといけないのかも知れませんが, めんどくさいのでこれ以上はやりません. CPUが全コア動いているので, 何かしらの並列効果は出ているのではないかと判断します.

Cpu0 : 42.2%us, 7.8%sy, 0.0%ni, 50.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu1 : 35.5%us, 7.1%sy, 0.0%ni, 57.4%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu2 : 16.0%us, 5.4%sy, 0.0%ni, 78.6%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu3 : 18.4%us, 5.0%sy, 0.0%ni, 76.6%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu4 : 15.9%us, 5.4%sy, 0.0%ni, 78.6%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu5 : 24.7%us, 5.4%sy, 0.0%ni, 69.8%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu6 : 15.5%us, 4.4%sy, 0.0%ni, 80.1%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu7 : 10.8%us, 4.1%sy, 0.0%ni, 85.1%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st

感想

ちょっと演習をしただけなので, どうということは言えませんが, 面白い言語だなぁと思いました. 間違いなく, 未来の人類にとって, こういうプログラミングが必要です. ふつうに仕事をしていると, PythonRubyでmapしたりfilterしたりというのが関数型Likeなコードを書く限界?もちろん, カーネルを書いていると, workerを定義して, そいつに対してメッセージを送って〜ということは自明にやりますが, 関数型色が強いかというとそういうことではない(関数型が分かる方が有利だけど). Erlangは, 再帰が分かりません!だと書けませんし, 上のコードでも数カ所で再帰を使っています. パターンマッチも使いますし, そういう意味で, 関数型欲の満たされる言語だなぁと思いました. すでにTwitterでは使われいてる?ようですし, Riak(Basho)や, 楽天のLeoFSなどでも使われているようで, きっと実用的な言語なのだろうと思います. でも僕は関数型ならHaskellの方に興味があるので, ErlangはしばらくBye Bye. 仕事で使えないかなぁ・・・.