PHP7の肝 zval構造体を読み解く

PHP7はPHP5と比べてものすごく高速化しているのは周知のとおりかと思います。

高速化の原理はhnwさんの記事に詳しく書いてあります。

http://d.hatena.ne.jp/hnw/20141207
http://www.slideshare.net/hnw/phpcon-kansai20150530

簡単に言うと、

* PHPにおける変数というのはzval構造体である
* そのzval構造体の構造が変わった

ということです。

図だけ見てわかったような気分になってもアレなので、実際にソースを見て確認してみましょう。

# 準備

内部構造を見るには、gdbでデバグしながら動かしてみるのがよいでしょう。

[やってみよう！PHPの内部構造の動きをC言語レイヤーで見る方法 - DQNEO起業日記](http://dqn.sakusakutto.jp/2013/04/php_gdb_zval_zvalue_value.html)

PHP7をビルドするには下記記事を参考にしてください。


[OSXで最新のPHPをビルドする方法](http://qiita.com/DQNEO/items/9aed9ee3136d5f3b7fbd)
[PHP7.0のDocker Imageを作る方法](http://qiita.com/DQNEO/items/21f184bd5c9620ac05ad)

# zval構造体の変更を、実際のソースコードを追ってみてみる

さて、実際のC言語のソースコードを見て変更箇所を確認してみたいと思います。

## PHP5.6のzval構造体

https://github.com/php/php-src/blob/php-5.6.9/Zend/zend.h#L334

PHP 5.6のzval構造体はこのようになっています。

```c:zend.h
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システムではメモリの配置に無駄が出てしまうのです。
以下のスライドの図解を見るとよくわかります。

<iframe src="//www.slideshare.net/slideshow/embed_code/key/3YD3C5Wr6lxPrB" width="425" height="355" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/hnw/phpcon-kansai20150530" title="PHP7で変わること ——言語仕様とエンジンの改善ポイント" target="_blank">PHP7で変わること ——言語仕様とエンジンの改善ポイント</a> </strong> from <strong><a href="//www.slideshare.net/hnw" target="_blank">Yoshio Hanawa</a></strong> </div>


1個目のメンバ変数zvalue_valueは共用体になっており、

```c:zend.h
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コードで

```php
$a = 1234;
```
と変数に値を代入すると、

```c
zval.value.lval = 1234;
```
のような感じで値がセットされます。

# PHP7のzval構造体

さてPHP7ではこれがどうなったのでしょうか。
PHP7では、zval構造体が`zend_types.h`で定義されています。

https://github.com/php/php-src/blob/9237a0de1cc40c4cc53c31a0396bf26249e1d5a9/Zend/zend_types.h#L113

```c: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構造体を見てみましょう。

```c:zend_types.h
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さんの解説記事をソースコードを見て確かめようのコーナーでした。