hnwの日記

PHPで時刻の計算が異常に遅い例


追記(2009/06/25):本件、PHP Bugs #46926でバグ報告しまして、PHP 5.2.10以降で修正されています。


PHPの日付/時刻関数にmktime関数というのがあります。年月日時分秒の6引数からいわゆるunix timeを求める関数ですが、これがPHPの最近のバージョンで並外れて遅いことがあるという話題です。毎度おなじみのパターンですが、今回もかなりのレアケースです(レアケースじゃなかったら既に誰か気づいているはずです)。

mktime - 日付を Unix のタイムスタンプとして取得する


説明
int mktime ([ int $hour [, int $minute [, int $second [, int $month [, int $day [, int $year [, int $is_dst ]]]]]]] )


与えられた引数に従って UNIX のタイムスタンプを返します。 このタイムスタンプは、Unix epoch(1970年1月1日00:00:00 GMT)から 指定された時刻までの通算秒を表す長整数です。


引数は右から順に省略することができます。省略された引数は、 ローカルの日付と時刻に従って、現在の値にセットされます。

PHP: mktime - Manual


この関数がおそらく他言語の同等の関数に比べて変わっている点は、各引数の範囲が特に定まっていないことです。時刻としてありえない範囲の数値が渡された場合は、自然な拡張として解釈されます。月に0が渡されたら前年の12月の意味、秒に-60が渡されたら1分前の意味になります。


また、PHP5.0.5までは日付周りのPHP関数の内部でmktime(3)をライブラリコールしていたのですが、PHP5.1.0からはunix timeの計算を自前で実装しています。この自前実装でunix timeを負の方向に自然に拡張することで、1970-01-01 00:00:00GMT以前の日時に正式に対応しました。これまでもmktime(3)が負のunix timeを返す環境では負のunix timeに対応していたのですが、PHP5.1.0からは言語として正式に負のunix timeに対応したということです。


ところで、このPHP5.1.0で新たに実装された時刻の処理がナイーブな実装で、mktime関数に負の引数を渡すとのんびりした処理をします。小さければ小さいほどのんびり処理します。私の手元の環境で実験してみました。

<?php
var_dump(mktime(9,0,-2147483647,1,1,1970));


上記PHPスクリプトで「1970年1月1日9時0分-2147483647秒JST」を計算してみます。

# time php-5.0.5 slow-mktime.php
int(-2147483647)

real    0m0.029s
user    0m0.019s
sys     0m0.010s
# time php-5.2.5 slow-mktime.php
int(-2147483647)

real    0m2.135s
user    0m2.123s
sys     0m0.009s
#
( ゚д゚)

(つд⊂)ゴシゴシ

(;゚д゚)

(つд⊂)ゴシゴシゴシゴシ

(;゚ Д゚)2秒…?


僕のマシンがいくら遅いといってもこれは遅すぎなのでは…。ちなみにPHP5.0.5はライブラリコールを使っており、この環境では負のunix timeを正しく返しているわけですが、PHPの自前実装版に比べると100倍くらい早いみたいですね。


別に誰も困らないとは思いますし、こんなレアケースを直すのに頑張るくらいなら他のことに時間を使った方がいいと思いますけどね。