PHP7はPHP5と比べてものすごく高速化しているのは周知のとおりかと思います。
高速化の原理はhnwさんの記事に詳しく書いてあります。
http://d.hatena.ne.jp/hnw/20141207
http://www.slideshare.net/hnw/phpcon-kansai20150530
簡単に言うと、
- PHPにおける変数というのはzval構造体である
- そのzval構造体の構造が変わった
ということです。
図だけ見てわかったような気分になってもアレなので、実際にソースを見て確認してみましょう。
準備
内部構造を見るには、gdbでデバグしながら動かしてみるのがよいでしょう。
やってみよう!PHPの内部構造の動きをC言語レイヤーで見る方法 - DQNEO起業日記
PHP7をビルドするには下記記事を参考にしてください。
OSXで最新のPHPをビルドする方法
PHP7.0のDocker Imageを作る方法
zval構造体の変更を、実際のソースコードを追ってみてみる
さて、実際のC言語のソースコードを見て変更箇所を確認してみたいと思います。
PHP5.6のzval構造体
PHP 5.6のzval構造体はこのようになっています。
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
メンバ変数が4つあります。
1個目のメンバ変数 zvalue_value value が実際の値です。
2個目のメンバ変数refcount というのは参照カウンタで、メモリーをGCするときに使うカウンタです。
実はこのようなメンバー4人衆構成は、32bitシステム時代に考えられた設計で、64bitシステムではメモリの配置に無駄が出てしまうのです。
以下のスライドの図解を見るとよくわかります。
1個目のメンバ変数zvalue_valueは共用体になっており、
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
zend_ast *ast;
} zvalue_value;
実際の値が共用体のどのメンバになるのかは、PHP変数のデータ型によって決まります。
- int型を格納する場合はlong lvalにセットされる
- stringを格納するときは struct { *val, len} の部分に文字列と長さがセットされる
- 配列の場合は HashTable *htにセットされる
- オブジェクトの場合はzend_object_value ojbにセットされる
という感じです。
つまり、PHPコードで
$a = 1234;
と変数に値を代入すると、
zval.value.lval = 1234;
のような感じで値がセットされます。
PHP7のzval構造体
さてPHP7ではこれがどうなったのでしょうか。
PHP7では、zval構造体がzend_types.h
で定義されています。
struct _zval_struct {
zend_value value; /* value */
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /* active type */
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved) /* call info for EX(This) */
} v;
uint32_t type_info;
} u1;
union {
uint32_t var_flags;
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
} u2;
};
だいぶ構造が変わっていることがわかります。
注目すべきは
- メンバ変数が3つになっている(value, u1, u2)
- 第一メンバ変数の型が
zend_value
になっている (PHP5時代のzvalue_valueは廃止) - 参照カウンタがなくなっている
です。
メンバ変数を削減したことによりメモリの使用量が減り、これが高速化に寄与しています。
では実際の値を格納しているzend_value構造体を見てみましょう。
typedef union _zend_value {
zend_long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
ZEND_ENDIAN_LOHI(
uint32_t w1,
uint32_t w2)
} ww;
} zend_value;
PHP5時代と比べると、
- zend_stringやzend_objectなどの新しい型が導入されている
- stringが、「文字列と文字列長の組」ではなくなってzend_string型へのポインタになっている。
のが大きな違いです。
見た目の印象として、例えば"zvalue_value" -> "zend_value"のように型名がだいぶわかりやすくなっているのがとてもいいですね。
以上、hnwさんの解説記事をソースコードを見て確かめようのコーナーでした。