読者です 読者をやめる 読者になる 読者になる

HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

Zend_Dateクラスを効率よく使うチャレンジ

PHP

Zend Frameworkに含まれているZend_Dateクラスは、インスタンス生成コストがとにかく高い。

どれくらい高いかというと、以下の2つのプログラムで比較してみるとなんとなく分かる。

<?php

$n = 10000;
if ($argc >= 2) {
    $n = (int)$argv[1];
}

set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library');
require_once('Zend/Date.php');

date_default_timezone_set('Asia/Tokyo');

$start = microtime(true);
for ($i = 0; $i < $n; ++$i) {
    $zd = new Zend_Date('2015-08-01 03:04:05');
}
$end = microtime(true);
printf("%8.5lf\n", $end - $start);
<?php

$n = 10000;
if ($argc >= 2) {
    $n = (int)$argv[1];
}

set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library');
require_once('Zend/Date.php');

date_default_timezone_set('Asia/Tokyo');

$start = microtime(true);
for ($i = 0; $i < $n; ++$i) {
    $zd = new DateTime('2015-08-01 03:04:05');
}
$end = microtime(true);
printf("%8.5lf\n", $end - $start);
  • test0-3.php: Zend_Dateオブジェクトをキャッシュしておいて、strtotimeとsetTimestampで対応
<?php

$n = 10000;
if ($argc >= 2) {
    $n = (int)$argv[1];
}

set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library');
require_once('Zend/Date.php');

date_default_timezone_set('Asia/Tokyo');

$zd = new Zend_Date();
$start = microtime(true);
for ($i = 0; $i < $n; ++$i) {
    $time = strtotime('2015-08-01 03:04:05');
    $zd->setTimestamp($time);
}
$end = microtime(true);
printf("%8.5lf\n", $end - $start);

これらを実行する。

$ for i in {1,2,5,8}{,00,000} ; do
>     printf "%d" $i
>     for t in test0-1.php test0-2.php test0-3.php ; do
>         php ${t} ${i} | awk '{printf(",%s", $0);}'
>     done
>     printf "\n"
> done
1, 0.03093, 0.00011, 0.00004
100, 0.23281, 0.00048, 0.00074
1000, 2.32891, 0.00825, 0.01088
2, 0.01160, 0.00012, 0.00005
200, 0.48956, 0.00336, 0.00224
2000, 5.51129, 0.01458, 0.02203
5, 0.02411, 0.00018, 0.00012
500, 1.08754, 0.00409, 0.00425
5000,11.39618, 0.03983, 0.06424
8, 0.03674, 0.00031, 0.00000
800, 2.20668, 0.00417, 0.00931
8000,19.13784, 0.07557, 0.11488
$ 

並べ替えてグラフにしてみるとこんな感じ。

f:id:hhelibex:20150801161410p:plain

とりあえず、欲しいものは「日時計算をして、その結果を日時文字列にしたもの」ということで話を進める。

Zend_Dateオブジェクトをキャッシュして使いまわすとよさそうということで、上記「test0-3.php」をベースに、以下のような計測をしてみる。

<?php

$n = 10000;
if ($argc >= 2) {
    $n = (int)$argv[1];
}

set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library');
require_once('Zend/Date.php');

date_default_timezone_set('Asia/Tokyo');

function _test(Zend_Date $zd) {
    return $zd->toString('YYYY-MM-dd');
}

$zd = new Zend_Date('2015-08-01 03:04:05');
//var_dump(_test($zd));
$start = microtime(true);
for ($i = 0; $i < $n; ++$i) {
    _test($zd);
}
$end = microtime(true);
printf("%8.5lf\n", $end - $start);
<?php

$n = 10000;
if ($argc >= 2) {
    $n = (int)$argv[1];
}

set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library');
require_once('Zend/Date.php');

date_default_timezone_set('Asia/Tokyo');

function _test(Zend_Date $zd) {
    $ts = $zd->getTimestamp();
    return date('Y-m-d', $ts);
}

$zd = new Zend_Date('2015-08-01 03:04:05');
//var_dump(_test($zd));
$start = microtime(true);
for ($i = 0; $i < $n; ++$i) {
    _test($zd);
}
$end = microtime(true);
printf("%8.5lf\n", $end - $start);
  • test1-3.php: Zend_Dateのgetメソッドで各フィールドを取得+sprintf関数
<?php

$n = 10000;
if ($argc >= 2) {
    $n = (int)$argv[1];
}

set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library');
require_once('Zend/Date.php');

date_default_timezone_set('Asia/Tokyo');

function _test(Zend_Date $zd) {
    $year = $zd->get(Zend_Date::YEAR);
    $month = $zd->get(Zend_Date::MONTH);
    $day = $zd->get(Zend_Date::DAY);
    return sprintf("%04d-%02d-%02d", $year, $month, $day);
}

$zd = new Zend_Date('2015-08-01 03:04:05');
$start = microtime(true);
for ($i = 0; $i < $n; ++$i) {
    _test($zd);
}
$end = microtime(true);
printf("%8.5lf\n", $end - $start);

さて、実行。

$ for i in {1,2,5,8}{,00,000,0000} ; do
>     printf "%d" $i
>     for t in test1-1.php test1-2.php test1-3.php ; do
>         php ${t} ${i} | awk '{printf(",%s", $0);}'
>     done
>     printf "\n"
> done

結果。

1, 0.00020, 0.00002, 0.00009
100, 0.01489, 0.00010, 0.01203
1000, 0.15603, 0.00634, 0.17088
10000, 1.28451, 0.04945, 1.37297
2, 0.00009, 0.00002, 0.00063
200, 0.02691, 0.00093, 0.04130
2000, 0.30510, 0.01436, 0.27488
20000, 2.93000, 0.12693, 3.37226
5, 0.00057, 0.00008, 0.00116
500, 0.08533, 0.00256, 0.07560
5000, 0.85879, 0.03124, 0.98879
50000,11.33206, 0.39485, 7.20063
8, 0.00063, 0.00009, 0.00074
800, 0.10203, 0.00917, 0.09432
8000, 0.86644, 0.03868, 0.81726
80000,12.23691, 0.57610,14.96780

f:id:hhelibex:20150801161436p:plain

さすがに各フィールドの値を取るのはコストが高いが、「test1-2.php」のケースが使えそうなレベル。

また、コードは省略するが、「test2-1.php」「test2-2.php」「test2-3.php」は時分秒まで含めるケース(「HH:mm:ss」「H:i:s」等の追加による)。

「test1-2.php/test2-2.php」はフォーマットに関係なくいいパフォーマンスを出しているが、Zend_Dateは時分秒まで含めるとそれだけコストが高くなる。

Zend_Dateクラスとは適度に付き合うのがよさそう(謎)。