SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Yahoo! JAPAN 黒帯シリーズ

PHP 7のパフォーマンスが高い理由

Yahoo! JAPAN 黒帯シリーズ 第1回 ~ 「PHPコア黒帯」による最新PHP 7の解説


変数の管理

 スクリプトで定義される$aのような変数は、整数型、浮動小数点型、文字列型、配列型、オブジェクト型、真偽型、null型、リソース型のすべてが、内部では同じ構造体zvalで管理されています。スクリプトで定義された変数だけではなく、PHPコアで扱われる値に関しても、zvalを使用することが多いです。このzvalがPHP 7になって大幅にチューニングされたことで、全体のパフォーマンス改善に貢献しています。

 データ構造がどのように変わったかは重要なので、それを見てみましょう。PHP 5のzvalの定義は以下の通りです。ソースファイルの中では1つの構造体として定義されていませんが、見やすくするためにまとめています。これ以降も、可視性を高めるために、実際の定義方法とは違う書き方をすることがあります。

typedef struct _zval_struct {
  union {
    long lval;
    double dval;
    struct {
      char *val;
      int len;
    } str;
    HashTable *ht;
    struct {
      zend_object_handle handle;
      zend_object_handlers *handlers;
    } obj;
  } value;
  zend_uint refcount;
  zend_uchar type;
  zend_uchar is_ref;
} zval;

 バイトアライメントの関係で8バイトの倍数となるので、サイズは24バイトになります。PHP 7のzvalは以下のとおりです。

typedef struct _zval_struct {
  union {
    long lval;
    double dval;
    zend_refcounted *counted;
    zend_string *str;
    zend_array *arr;
    zend_object *obj;
    zend_resource *res;
    zend_reference *ref;
    void *ptr;
  } value;
  union {
    struct {
      zend_uchar type;
      zend_uchar flags;
    };
    zend_uint type_info;
  };
  zend_uint reserved;
} zval;

 zvalのサイズが16バイトに減っています。つまり、PHP 7になるとzvalのサイズがPHP 5の時の2/3になっていますが、24バイトがどのようにして16バイトに減ったのか、PHP 5からPHP 7への変更をみてみましょう。

 整数型や浮動小数点型、配列型やオブジェクト型を管理する共用体valueが構造体を持たなくなったことにより、8バイト減っています。参照やコピーオンライトを管理するrefcountとis_refがなくなったので、6バイト減っています。変数の種類を管理するtypeは、管理方法が少し異なり、2バイト増えています。新しく加わったメンバはreservedで、この4バイトも増えています。これらを計算すると、以下のように24バイトから16バイトへとなります。

24 - 8 - 6 + 2 + 4 = 16

 PHP 7では、整数型、浮動小数点型、真偽型などがコピーオンライトではなくなったので、refconutとis_refをzvalで持つことをやめました。コピーオンライトになっており、参照を管理している構造体zend_refcountedを持っているのは、文字列型、配列型、オブジェクト型、リソース型、参照型だけです。PHP 5では最大公約数的にどの型も等しく持っていたメンバを、PHP 7ではそれぞれの変数の種類によって最適化したといってよいでしょう。

 いくつか代表的な型の構造体をみてみます。文字列型の構造体は以下です。

typedef struct _zend_string {
  zend_refcounted gc;
  zend_ulong h;
  size_t len;
  char val[1];
} zend_string;

 gcは参照を管理しているメンバで、hには文字列のハッシュ値が入っています。lenは文字列の長さで、valは文字列のサイズによって大きくなります。ハッシュ値を持っているので、探索する時にパフォーマンスが高くなるでしょう。

 次は配列型の構造体です。PHP 7から使われている構造体で、PHP 5では、zvalの中にHashTableがあったので必要ありませんでした。

typedef struct _zend_array {
  zend_refcounted gc;
  HashTable ht;
} zend_array;

 文字列型の構造体と同じように、参照を管理するgcがあります。htが配列のキーと値を管理しているハッシュテーブル構造体です。PHP 5ではハッシュテーブル構造体がzvalのメンバでしたが、PHP 7からはzvalからarrayポインタを1つたどるのでアクセスが悪くなったと言えます。ハッシュテーブルについては、のちほど詳しく説明します。

 次はオブジェクト構造体です。まずはPHP 5から紹介します。

typedef struct _zend_object {
  zend_class_entry *ce;
  HashTable *properties;
  HashTable *guards;
} zend_object;

 PHP 7のオブジェクト構造体は以下です。

typedef struct _zend_object {
  zend_refcounted gc;
  uint32_t handle;
  zend_class_entry *ce;
  const zend_object_handlers *handlers;
  HashTable *properties;
  HashTable *guards;
  zval properties_table[1];
} zend_object;

 比べてみると、PHP 7になるとメンバが増えています。これは、PHP 5ではあった構造体zend_object_valueがなくなったからで、オブジェクト識別子handleがzend_objectに移動しました。PHP 7からはproperties_tableが新しく増えており、オブジェクトを生成する時に、プロパティの数だけ動的に配列が確保されます。プロパティの値はプロパティと共にハッシュテーブルのpropertiesに入っています。プロパティだけが管理されたproperties_tableは連続した配列なので、プロパティを探索する際には早くなります。

 PHP 7でもコピーオンライトを用いる場合に使用する構造体をみてみましょう。

struct _zend_refcounted {
  uint32_t refcount;
  union {
    struct {
      zend_uchar type;
      zend_uchar flags;
      uint16_t gc_info;
    } v;
    uint32_t type_info;
  } u;
};

 参照カウントrefcountはこの構造体で持っています。typeとflagsはzvalでも持っていますが、こちらでも持っているので、もとのzvalをたどるというオーバーヘッドがなくなっています。

 次は具体的な例で、変数の扱いがどのように変わったのかを比べてみましょう。まずは整数型から。

$a = 1;

 上記のスクリプトでは、PHP 5とPHP 7のメモリイメージは以下となります。

PHP 5での整数のzval
PHP 5での整数のzval
PHP 7での整数のzval
PHP 7での整数のzval

 比べてみると、PHP 7の方がサイズが小さいことが改めて分かります。次に別の変数に代入してみます。

$b = $a;

 上記のスクリプトでは、PHP 5とPHP 7のメモリイメージは以下となります。

PHP 5での整数のzval代入
PHP 5での整数のzval代入
PHP 7での整数のzval代入
PHP 7での整数のzval代入

 PHP 5ではzvalが1つしかありませんが、PHP 7ではzvalが2つになっていることが分かるでしょう。PHP 7では整数型に対してコピーオンライトではなくなったので、実体を持っています。使用するメモリを比べてみると、PHP 5では40バイトあったものが、PHP 7では32バイトとなり、少なくなっています。

$c = $a;

 さらに値を入れていくとこうなります。

PHP 5での整数のzval代入2
PHP 5での整数のzval代入2
PHP 7での整数のzval代入2
PHP 7での整数のzval代入2

 PHP 5では引き続きzvalが1つしかありませんが、PHP 7ではzvalが3つになっています。使用するメモリを比べてみると、PHP 5では48バイトあり、PHP 7では48バイトとなり、同じになりました。このように、変数の代入が増えていくにつれて、PHP 5の方が使用するメモリは少なくなっていきますが、PHP 5では値を参照するためにはポインタをたどっていく必要があり、これがオーバーヘッドとなっていました。

 次に文字列型をみてみましょう。

$a = 'abcdefgh';

 上記のスクリプトでは、PHP 5とPHP 7のメモリイメージは以下となります。

PHP 5での文字列のzval
PHP 5での文字列のzval
PHP 7での文字列のzval
PHP 7での文字列のzval

 PHP 5では40バイトを使用しており、PHP 7では48バイトを使用しています。PHP 7の方が多くメモリを使用していますが、文字列の実体へはPHP 5ではポインタを3回たどるのに対して、PHP 7ではポインタを2回しかたどりません。これは文字列の実体がzval_string内にあるためです。ポインタをたどる回数が少ないことで、メモリアクセスの効率がよくなっています。

次のページ
ハッシュテーブル

修正履歴

この記事は参考になりましたか?

Yahoo! JAPAN 黒帯シリーズ連載記事一覧

もっと読む

この記事の著者

蒋池 東龍(ヤフー株式会社)(コモイケ トウリュウ)

ヤフー株式会社 COO 事業推進本部 プロダクト開発 黒帯 PHPコア1999年セガ(当時セガ・エンタープライゼス)に新卒入社しプログラマーとして「ジェットセットラジオ」「Jリーグ スペクタクルサッカー」「J.LEAGUEプロサッカークラブをつくろう!」の開発に従事。2004年10月ヤフー株式会社入...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/8492 2015/05/15 10:43

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー