アクセスログに『Excel vba 再現性』なんてのがあったので適当に書いてみることに, 以下思いつくままに書いているので正確性は保障できないですが, 何か思うところがあればどんどん指摘してくれると幸いです
プログラムで乱数の再現性を気にするという要請は2つに分けることが出来る, 再現して欲しくない場合と再現して欲しい場合である
後者は『乱数』という言葉に一見矛盾しているかもしれないが, 乱数関数が同じ数列を返してくれるとエラーの発生条件を固定できるので, 乱数が絡んでいるバグのデバグに役立つことがある
そもそも乱数といっても一般に使われるのは擬似乱数といわれる単なる漸化式で表された数列に過ぎない
数列が次にとる値の挙動が複雑怪奇で次の値を予測しづらい, 特に乱数の剰余を取ったりして使うと上位ビットの情報はユーザから隠蔽されるよく分からないからこそ乱数として利用できるという理屈である
余談ではあるが, 昔の某処理系の乱数は偶数の後には必ず奇数が, 奇数の後には必ず偶数が出現していたそうである, mod 2で使ってたら全然乱数になっていないので残念でしただったそうである
で, 乱数を再現したい場合の解は単純明快である, この数列のこれを乱数のseedという初期値を適当に固定してしまえばいいのである
C言語やPerlならsrand関数とか, ExcelのVBAを含むBasic系だとRandomizeステートメントとかに固定の値を与えれば, 常に同じ数列が得られるのである
逆に乱数を再現させたくない場合, これは多少面倒である
単一のマシンで実行することを前提にしていれば, 同一プログラムを同時に実行されることはないと仮定して, 時刻をseedとすればいい
C言語やPerlだったら1970/01/01 00:00 UTCからの経過秒数time関数, Basic系ならその日の00:00(多分現地時間)からの経過秒数Timer関数を上記seed初期化関数に食わせればよい
なお, 最近のPerlはsrandを呼ばなかった場合は適宜初期化してくれるし, Basic系なら引数なしでRandomizeを呼べば同等の処理をしてくれる
Windows Vistaは知らないxpのソリティアで新しいゲーム[F2]を押しっぱなしにしてみると, 1秒毎にカードの配置が換わり, 1秒経たないうちは同じ配置が現れる, この類の初期化をしていることが観察できる好例だと思われる
さて, これでは困る例なんかも考えられる
たとえばWebサーバ上にお御籤CGIを設置することを考える, このCGIでは1日1回おみくじの得点でランキングを作って公開しているとしよう
1日1回しか実行できないので日付の変わり目には駆け込み実行するひとや, 日付が変わった瞬間実行する人がいて, time関数が同じ値を返すという意味秒レベルで同じ時刻におみくじを引く人がそこそこの確率で発生すると考えられる
そこにたまたまいい得点を与えるような乱数が潜んでいた場合, ランキングに有効数字10桁ぐらいまで全く同じ得点の人が並ぶことになってしまい, 興醒めになってしまう
少々強引な例ではあるが, こういう場合に(ほぼ)同時実行さっる各プログラムで別の乱数を使いたいという要請が考えられる
問題は同じ値を乱数のseedとして使っていたことにあるのだから, それらの実行間でも違う値を使えばよい
幸いgetpid(2)システムコールやPerlの$$に入っている以下, PIDプロセスIDがそのような性質を持っているのでこの値に桁数を補うためにtime()を適当に左シフトした値でも加えてあげればかなりよいseedが得られるはずである
なお, 時刻とPIDの単純なxorを取るのは愚策である, 1秒後に1大きいプロセスIDで実行されたときに高い確率で同じ乱数が出てくるのは目に見えている
さて, これでも不十分な例をでっち上げることも不可能ではない, このサービスが思いのほか好評でリクエストを捌き切れず, 複数台の何も考えずにラウンドロビンするとリクエストごとの負荷の差に起因して特定のノードに負荷が集中する事態になりがちであるという指摘はとりあえず今は無視負荷分散環境でラウンドロビン実行しているとしてみよう
この場合, 各ノードのプロセスの累計数はだいたい同じ値をとり, PIDも似たような値を同時期にとると考えられる(実際にはそうでもないけどね)
この場合, 時刻とPIDに頼った乱数列初期化ではいまくいかないことを示唆している, ホスト名か自分 or 相手のIPアドレスのハッシュでもseedに加えるのだろうか, 面倒だなぁ…
いっそのこと/dev/大量のリクエストが来る前提なのでrandomでエントロピープールを使い果たした場合に待たされるのはパフォーマンス低下の原因となるけど, そもそも通信が大量にあるならエントロピープールは枯れないのかな? この辺不明urandomあたりを読んで初期化するのだろうか
なんでこんなことを考えてたかというと, MPIでsrand(getpid())したデバッグというか単にsleepの引数をずらしてタイミングのずれを恣意的に起こしたかっただけ, rank使えといわれたらそれまで乱数を使ったら同じ乱数列がノード間で発生してあ~あと思ったからなだけなんですけどね
Comments (0):