hnwの日記

SplFixedArrayはPHP標準の配列よりメモリを消費しない

PHP5.3.0から実装されたSplFixedArrayというSPLクラスがあります。これはマニュアルによれば下記のようなクラスです。

SplFixedArray クラスは配列の主要な機能を提供します。 SplFixedArray と通常の PHPの配列との主な違いは、 SplFixedArray は固定長であって、整数値で指定した範囲内の添字しか使用できないところです。これにより、より高速な配列の実装が可能となりました。


http://docs.php.net/manual/ja/class.splfixedarray.php


制限はあるけれども高速な配列もどきのクラスだと紹介してありますね。このクラスについて少し調べてみました。

SplFixedArrayの速度

まずはSplFixedArrayが本当に速いのかどうか、下記のようなプログラムで実験してみました。実験はPHP5.3.0で行いました。

<?php
for($size = 5; $size < 20000; $size *= 2) {
    echo PHP_EOL . "Testing size: $size" . PHP_EOL;

    unset($container);
    $start_time = microtime(true);
    for ($container = array(), $i = 0; $i < $size; $i++) {
      $container[$i] = $i;
    }
    echo "Array():    " . (microtime(true) - $start_time) . PHP_EOL;

    unset($container);
    $start_time = microtime(true);
    for($container = new SplFixedArray($size), $i = 0; $i < $size; $i++) {
      $container[$i] = $i;
    }
    echo "SplArray(): " . (microtime(true) - $start_time) . PHP_EOL;
}


配列とSplFixedArrayの全要素に値を書き込むテストです。結果は下記の通りです。

Testing size: 5
Array():    2.6941299438477E-5
SplArray(): 4.1961669921875E-5

Testing size: 10
Array():    5.0067901611328E-6
SplArray(): 5.9604644775391E-6

Testing size: 20
Array():    9.0599060058594E-6
SplArray(): 7.1525573730469E-6

Testing size: 40
Array():    2.288818359375E-5
SplArray(): 1.0967254638672E-5

Testing size: 80
Array():    2.6941299438477E-5
SplArray(): 1.7166137695312E-5

Testing size: 160
Array():    6.1988830566406E-5
SplArray(): 3.1948089599609E-5

Testing size: 320
Array():    0.0020380020141602
SplArray(): 6.7949295043945E-5

Testing size: 640
Array():    0.00023484230041504
SplArray(): 0.00011801719665527

Testing size: 1280
Array():    0.00044584274291992
SplArray(): 0.00022792816162109

Testing size: 2560
Array():    0.00090503692626953
SplArray(): 0.00048589706420898

Testing size: 5120
Array():    0.0018289089202881
SplArray(): 0.0011360645294189

Testing size: 10240
Array():    0.004019021987915
SplArray(): 0.002223014831543


クラス生成のオーバーヘッドがあるので常に配列より有利というわけにはいきませんが、確かにSplFixedArrayは高速です。ベンチマークとしてはアバウトな実験ですが、配列サイズが20を超えたあたりで配列よりSplFixedArrayの方が高速になり、配列サイズ160あたりからはSplFixedArrayの方が倍くらい高速になるようです。


内部構造としては、SplFixedArrayはzvalのポインタの配列として実現されています。一方、PHPの通常の配列は、配列としての構造と連想配列としての構造をそれぞれlinked listで持つような複雑な構造になっています。このような構造の差が上記の速度差の原因だと考えられます。


ただし、SplFixedArrayはクラスなので、要素へアクセスする際のメソッド呼び出しのオーバーヘッドが避けられません。実験したところでは、既存要素の読み書きは配列の方が高速なようです。

SplFixedArrayのメモリ消費

しかし、実際のアプリケーションでは他の処理も行うはずですから、この程度の速度差は隠蔽されることが多いのではないでしょうか。また、CPUよりもメモリの方がリソースとして貴重な環境も多いかと思います。そこで、配列とSplFixedArrayのメモリ消費の差を測定してみます。

<?php
for($size = 5; $size < 20000; $size *= 2) {
    echo PHP_EOL . "Testing size: $size" . PHP_EOL;

    unset($container);
    $start_memory = memory_get_usage();
    for ($container = array(), $i = 0; $i < $size; $i++) {
      $container[$i] = $i;
    }
    echo "Array():    " . (memory_get_usage() - $start_memory) . PHP_EOL;

    unset($container);
    $start_memory = memory_get_usage();
    for($container = new SplFixedArray($size), $i = 0; $i < $size; $i++) {
      $container[$i] = $i;
    }
    echo "SplArray(): " . (memory_get_usage() - $start_memory) . PHP_EOL;
}


上記プログラムを実行したところ、次のような結果になりました。

Testing size: 5
Array():    692
SplArray(): 532

Testing size: 10
Array():    936
SplArray(): 604

Testing size: 20
Array():    1708
SplArray(): 924

Testing size: 40
Array():    3276
SplArray(): 1564

Testing size: 80
Array():    6420
SplArray(): 2844

Testing size: 160
Array():    12700
SplArray(): 5404

Testing size: 320
Array():    25236
SplArray(): 10524

Testing size: 640
Array():    50332
SplArray(): 20764

Testing size: 1280
Array():    100496
SplArray(): 41244

Testing size: 2560
Array():    200852
SplArray(): 82216

Testing size: 5120
Array():    401596
SplArray(): 164144

Testing size: 10240
Array():    803024
SplArray(): 327988


SplFixedArrayはPHP標準の配列に比べ、半分以下のメモリしか消費しません。これは速度差より断然インパクトのある差に感じられます。

まとめ

連想配列アクセスが不要な場合でも、これまでのPHPには配列の構造が1種類しかありませんでした。しかし、PHP5.3.0からは配列アクセスに特化したSplFixedArrayというクラスが提供されるようになりました。


SplFixedArrayの真価は速度よりもメモリ消費の少なさにあると個人的には考えています。大規模なデータを扱う際には実用的なクラスなのではないでしょうか。