PHPのデータ構造

配列よりオブジェクトの方がパフォーマンスが良いという話を聞き、気になったので調べてみました。

スカラー変数

まず、PHPスカラー変数の復習。

PHPスカラー変数は、以下のように、シンボルテーブルからzval構造体にアクセスして、値を取得します。

<?php $a = 1; ?>

変数のコピーでは、値をコピーするのではなく、同じzval構造体を指し、refcountが1追加されます。

<?php $b = $a; ?>

参照渡しでは、同じzval構造体を指し、refcountが1追加、is_refが1になります。

<?php $b = &$a; ?>


配列

次に配列。PHPの配列は、順番を持ったハッシュです。

配列$aの"x"にアクセスするには、まず$aが指すzval構造体にアクセスし、そのzval構造体のzvalue_value共用体にあるハッシュテーブルから、指定されたキーのzval構造体にアクセスします。日本語って難しい。。。

<?php echo $a['x']; ?>

配列を新規で作成していくと、中身の値は同じでも、新たに「zval構造体→ハッシュテーブル→zval構造体」が作成されます。

<?php
    $a = array('x' => 'xxxx', 'y' => 'yyyy');
    $b = array('x' => 'xxxx', 'y' => 'yyyy');
?>

オブジェクト

最後にオブジェクトです。オブジェクトは、クラスという静的な型が存在するので、作成するだけなら配列よりもメモリを消費しない構造になっています。

zval構造体のzvalue_value共用体のzend_object_value構造体からそのオブジェクトのプロパティハッシュテーブルにアクセスするんですが、最初は、以下のようにデフォルトのzval構造体を指すようになっています。

<?php
    $a = new Test;
    $b = new Test;
?>

プロパティの値を変更すると、通常のスカラー変数と同様に、元のzval構造体のrecountが減り、新たにzval構造体が作成されます。

<?php
    $b->x = "xxxx";
    $b->y = "yyyy";
?>

この事を、GDBを使って確かめてみます。
PHPの実行をPHPのソースコード(C言語)のレベルで見る方法を参考に、次のようなPHPプログラムを作成してTestクラスのpの中身を見てみます。
参考の記事と同じように、breakpointは、intval関数にしました。

<?php
class Test
{
    public $p="aaa...(省略)";
}

$a = new Test;
intval($a->p);

$b = new Test;
intval($b->p);

$c = new Test;
intval($c->p);

$c->p = "bbb...(省略)";
intval($c->p);
?>

結果は、以下。

//$a = new Test
(gdb) p **num
$1 = {value = {lval = 139376840, dval = 6.8861308469912085e-316, str = {
      val = 0x84eb8c8 "\n", 'a' <repeats 199 times>..., len = 401}, 
    ht = 0x84eb8c8, obj = {handle = 139376840, handlers = 0x191}}, 
  refcount = 3, type = 6 '\006', is_ref = 0 '\0'}

//$b = new Test
(gdb) p **num
$2 = {value = {lval = 139376840, dval = 6.8861308469912085e-316, str = {
      val = 0x84eb8c8 "\n", 'a' <repeats 199 times>..., len = 401}, 
    ht = 0x84eb8c8, obj = {handle = 139376840, handlers = 0x191}}, 
  refcount = 4, type = 6 '\006', is_ref = 0 '\0'}

//$c = new Test
(gdb) p **num
$3 = {value = {lval = 139376840, dval = 6.8861308469912085e-316, str = {
      val = 0x84eb8c8 "\n", 'a' <repeats 199 times>..., len = 401}, 
    ht = 0x84eb8c8, obj = {handle = 139376840, handlers = 0x191}}, 
  refcount = 5, type = 6 '\006', is_ref = 0 '\0'}

//$c->p = "bbb...(省略)"
(gdb) p **num
$4 = {value = {lval = 139374176, dval = 6.8859992279031564e-316, str = {
      val = 0x84eae60 "\n", 'b' <repeats 199 times>..., len = 401}, 
    ht = 0x84eae60, obj = {handle = 139374176, handlers = 0x191}}, 
  refcount = 2, type = 6 '\006', is_ref = 0 '\0'}

以上のように、refcountが最初3だったものが、4、5と増え、$c->pを変更したときに、新たなzval構造体が作成されたことが分かります。

結論

PHPの変数は、zval構造体で表現されているため、コピーやデフォルトのプロパティなど同じものに対しては、同じzval構造体を参照する。
そのため、配列では作る度に新たに値を格納するzval構造体が生成されるのに対して、オブジェクトは、最初は値を格納するzval構造体は一つであるため、オブジェクトの方がメモリ消費量が少なくなる。

参考