PHP と srand と seed
億のレコード数を目指すサービスで、
レコードの uniq id を生成する PHP コードをみて絶望したっ!
function createUniqID() { srand( (double)microtime()*1000000 ); <略>
それは勘弁して… ・゚・(ノД`)・゚・
(検索で見つけたどこかのブログからコピペしてきたそうで)
最近の PHP で srand を呼ぶのは同じ乱数列を再現させたい時位なので、
今回の場合は srand をただ削るだけですけど。
問題の解説
microtime() に対してこういう処理をすると、
- microtime() => string(21) "0.49181000 1218109364"
- (double)microtime() => float(0.49181)
- (double)microtime()*1000000 => float(491810)
となるので、seed が 0 - 999,999 の間の数値に収まり、かつ、
srand の後に続く rand の乱数列がその seed で決まるので、
この関数が返す ID は 100万種に限定されてしまいます。
PHP5 から get_as_float の引数が導入されたので、 microtime(true)
とすれば、
microtime が内部で呼ぶ gettimeofday がタスクのスイッチより分解能がある
という確信がない限り、これだけに頼る時点で危険な気がします。
そもそも PHP は結構前*1から srand を呼ばずに rand をいきなり呼んでも、
内部で勝手に srand 相当の処理が動くようになっています。
更に、この勝手に動く(seed を明示的に指定しない)srand の処理*2は、 (大雑把だけど)
1,000,000 * | (f1(sec xor usec) - f2(thread_id or pid ))*A | xor unixtime * pid MODMULT の処理が複雑なので f1, f2 でごまかし。^^;
の値を seed にしていて、下手な seed を渡すより、
これに任せた方がずっと良いです。
(pid と時刻の値を混ぜるのは、結構定石)
昔話
大昔の PHP では srand を明示的に呼ぶ必要があったので、
srand( (double)microtime()*1000000 );
というのは、よく使われていたようです。
microtime の仕様が今と異なるのか、はたまた、
そんなに沢山の乱数は要らないと思ってるのかワカラナイけど、
google で検索すると、このコードが沢山見つかります。
これはまずいと指摘する人もいるのですが、
Just using microtime() * 1000000 only results in 1000000 possible seeds (and less on some platforms as noted)
まだまだ、Web 上ではこの古いサンプルが目立つので
同じ間違いが色んな場所で繰り返されるんでしょうね。