phpの配列を文字列にするあれこれ

DoctrineでResultCacheを使うときに、timeを扱うフィールドがあると効果がないので、キャッシュキー生成の部分をオーバーライドしている。

元のコードは

<?php
// 〜略〜
    public function calculateQueryCacheHash()
    {
        $dql = $this->getDql();
        $hash = md5($dql . var_export($this->_pendingJoinConditions, true) . 'DOCTRINE_QUERY_CACHE_SALT');
        return $hash;
    }
// 〜略〜

こんなふうになってて、md5とvar_exportって重くね?ってふと気になった。

そこで、実際の所どうなのか、僕の思いつく配列を文字列に変換するあれこれの方法でパフォーマンスを調べてみた。

<?php
require_once 'Benchmark/Timer.php';
$array = array(
  'a'    => '1234567',
  'hoge' => 345,
  'time' => '12/21 00:30',
);
$b = new Benchmark_Timer();
$b->start();
for ($i = 0; $i < 10000; $i++) {
  $hash = serialize($array);
}
echo "serialize: $hash (".strlen($hash) .")\n";
$b->setMarker('serialize');

for ($i = 0; $i < 10000; $i++) {
  $hash = json_encode($array);
}
echo "json_encode: $hash (".strlen($hash) .")\n";
$b->setMarker('json_encode');

for ($i = 0; $i < 10000; $i++) {
  $hash = implode(":", $array);
}
echo "implode: $hash (".strlen($hash) .")\n";
$b->setMarker('implode');

for ($i = 0; $i < 10000; $i++) {
  $hash = implode(":", array_keys($array)) . "=>" . implode(":", $array);
}
echo "implode + array_keys: $hash (".strlen($hash) .")\n";
$b->setMarker('implode + array_keys');

for ($i = 0; $i < 10000; $i++) {
  $hash = var_export($array, true);
}
echo "var_export: $hash (".strlen($hash) .")\n";
$b->setMarker('var_export');

$b->stop();

$b->display();

結果

serialize: a:3:{s:1:"a";s:7:"1234567";s:4:"hoge";i:345;s:4:"time";s:11:"12/21 00:30";} (75)
json_encode: {"a":"1234567","hoge":345,"time":"12\/21 00:30"} (48)
implode: 1234567:345:12/21 00:30 (23)
implode + array_keys: a:hoge:time=>1234567:345:12/21 00:30 (36)
var_export: array (
  'a' => '1234567',
  'hoge' => 345,
  'time' => '12/21 00:30',
) (73)
                                                                                                                                    • -
marker time index ex time perct
                                                                                                                                    • -
serialize 1285050970.27137800 0.027326 7.44%
                                                                                                                                    • -
json_encode 1285050970.30083500 0.029457 8.02%
                                                                                                                                    • -
implode 1285050970.32389300 0.023058 6.28%
                                                                                                                                    • -
implode + array_keys 1285050970.39212300 0.068230 18.58%
                                                                                                                                    • -
var_export 1285050970.44748400 0.055361 15.07%
                                                                                                                                    • -

大方の予想通り,Doctrineの方法(var_export)は遅い!

ここで、あ!そうかDoctrineはオブジェクトにも対応してるんだな!ってことに気づいてオブジェクトにも対応するように修正

<?php
require_once 'Benchmark/Timer.php';
$array = array(
  'a'    => '1234567',
  'hoge' => 345,
  'time' => '12/21 00:30',
  'obj'  => new Test(5, 'abcd', '123'),
);
class Test
{
  public $a;
  protected $b;
  private $c;

  public function __construct($a, $b, $c)
  {
    $this->a = $a;
    $this->b = $b;
    $this->c = $c;
  }
}
//test
$b = new Benchmark_Timer();
$b->start();
for ($i = 0; $i < 10000; $i++) {
  $hash = serialize($array);
}
echo "serialize: $hash (".strlen($hash) .")\n";
$b->setMarker('serialize');

for ($i = 0; $i < 10000; $i++) {
  $hash = json_encode($array);
}
echo "json_encode: $hash (".strlen($hash) .")\n";
$b->setMarker('json_encode');

for ($i = 0; $i < 10000; $i++) {
  $hash = var_export($array, true);
}
echo "var_export: $hash (".strlen($hash) .")\n";
$b->setMarker('var_export');

$b->stop();

$b->display();

結果

serialize: a:4:{s:1:"a";s:7:"1234567";s:4:"hoge";i:345;s:4:"time";s:11:"12/21 00:30";s:3:"obj";O:4:"Test":3:{s:1:"a";i:5;s:4:"*b";s:4:"abcd";s:7:"Testc";s:3:"123";}} (158)
json_encode: {"a":"1234567","hoge":345,"time":"12\/21 00:30","obj":{"a":5}} (62)
var_export: array (
  'a' => '1234567',
  'hoge' => 345,
  'time' => '12/21 00:30',
  'obj' => 
  Test::__set_state(array(
     'a' => 5,
     'b' => 'abcd',
     'c' => '123',
  )),
) (172)
                                                                                                                            • -
marker time index ex time perct
                                                                                                                            • -
serialize 1285052647.37092000 0.041734 10.71%
                                                                                                                            • -
json_encode 1285052647.40332300 0.032403 8.31%
                                                                                                                            • -
var_export 1285052647.49001500 0.086692 22.25%
                                                                                                                            • -

なんと、、、全然遅い。

僕の結論

serializeのほうがjsonより少し早いけど、データの小ささ、多言語との互換性を考慮すると、jsonを使うのがよさそうな気がする。
ただし、Objectを含み、privateメンバの値も重要だよってときは、serializeが良い。
というか結局本気で取り組むなら、inplode, json, 無変換を臨機応変に使い分けるべしって感じ



おまけmd5を付けた場合

                                                                                                                          • -
marker time index ex time perct
                                                                                                                          • -
serialize 1285054112.44181000 0.041134 10.52%
                                                                                                                          • -
serialize+md5 1285054112.50591500 0.064105 16.40%
                                                                                                                          • -
json_encode 1285054112.53768200 0.031767 8.13%
                                                                                                                          • -
json_encode+md5 1285054112.59036000 0.052678 13.47%
                                                                                                                          • -
var_export 1285054112.68119600 0.090836 23.23%
                                                                                                                          • -
var_export + md5 1285054112.79160600 0.110410 28.24%
                                                                                                                          • -

var_exportを使っちゃうより全然早いので、復元しない限りは使ったほうがよいきがする



こんな感じで、結構アクセスが多くなりそうなサービスの開発にDoctrineを選択したことを今は正直後悔している。

Doctrineはイニシャルの開発工数は慣れれば減りそうだが、パフォーマンスを気にし始めるとチューニングで死ぬ。
だったら、いろはを知っているPropelを選べばよかった。。。