Reducing size of zval structure
zvalをダイエット
してみた
hnw
第五回闇PHP勉強会 (2014/3/15)

発表資料
自己紹介
❖ @hnw!
❖ 勤務先:KLab株式会社!
❖ カレーとバグが大好物!
❖ 好きなdouble値:NaN
発想
❖ NaN boxingってカッコいい!
❖ doubleの一部にポインタや整数を詰め込む技!
❖ PHPにも応用できないか?!
❖ サイズ減らす点だけなら真似できそうだ!
❖ やってみた
アジェンダ
❖ PHPのzval概要!
❖ zvalをダイエットする!
❖ さらなるダイエットへの道
❖ PHPのzval概要!
❖ zvalをダイエットする!
❖ さらなるダイエットへの道
PHPの変数を考える
❖ C:静的型付け言語!
❖ コンパイル時に型が決まる!
❖ 変数に型情報をつける必要が無い!
❖ PHP:動的型付け言語!
❖ 実行時まで型が決定できない!
❖ 変数値と型情報のペアを持ち回る必要がある
PHPの変数
❖ C言語レベルでは、zval構造体で管理されている
typedef struct _zval_struct zval;	
!
struct _zval_struct {	
zvalue_value value; /* 変数値 */	
zend_uint refcount__gc; /* 参照カウンタ */	
zend_uchar type; /* 変数型 */	
zend_uchar is_ref__gc; /* 参照渡しされたか */	
};
zvalのtypeに入る値
❖ 変数型ごとに異なるtype値が定義されている
#define IS_NULL 0	
#define IS_LONG 1	
#define IS_DOUBLE 2	
#define IS_BOOL 3	
#define IS_ARRAY 4	
#define IS_OBJECT 5	
#define IS_STRING 6	
#define IS_RESOURCE 7	
#define IS_CONSTANT 8	
#define IS_CONSTANT_ARRAY 9	
#define IS_CALLABLE 10
変数値に対応する共用体
❖ 全ての変数型に対応する値を格納できる
typedef union _zvalue_value {	
long lval; /* intとboolとresource */	
double dval; /* float */	
struct {	
char *val; /* 文字列 */	
int len; /* 文字列長 */	
} str; /* string */	
HashTable *ht; /* array */	
zend_object_value obj; /* object */	
} zvalue_value;
zvalざっくり概要
❖ zval:PHPの変数1個に対応する構造体!
❖ メンバ変数!
❖ 型は何か!
❖ 値は何か!
❖ 参照カウンタ!
❖ 参照渡しされたか
❖ PHPのzval概要!
❖ zvalをダイエットする!
❖ さらなるダイエットへの道
zvalのサイズ減=高速化?
❖ 仮説:zvalのサイズを減らすとPHPが性能アップする!
❖ zvalのサイズって減らす余地あるのかしら?
zvalのサイズ
❖ 64bit環境では24byteになる
typedef struct _zval_struct zval;	
!
struct _zval_struct {	
zvalue_value value; /* 128bit、変数値 */	
zend_uint refcount__gc; /* 32bit、参照カウンタ */	
zend_uchar type; /* 8bit、変数型 */	
zend_uchar is_ref__gc; /* 8bit、参照渡しされたか */	
}; /* 196bit */
_zvalue_valueのサイズ
typedef union _zvalue_value {	
long lval; /* 64bit */	
double dval; /* 64bit */	
struct {	
char *val; /* 64bit */	
int len; /* 32bit */	
} str; /* 128bit */	
HashTable *ht; /* 64bit */	
zend_object_value obj; /* 128bit */	
} zvalue_value;	
!
typedef struct _zend_object_value {	
zend_object_handle handle; /* 32bit */	
const zend_object_handlers *handlers; /* 64bit */	
} zend_object_value; /* 128bit */	
!
typedef unsigned int zend_object_handle;
64bit環境のzvalはスカスカ
❖ zval構造体の内訳!
❖ 変数値に96bit(12byte)!
❖ 参照カウンタ・その他で32bit+9bit!
❖ 実質18byteだが、alignmentの都合で24byte消費!
❖ 9bit節約すれば16byteにできるはず
zvalダイエット図解
_zvalue_value
refcount__gc unused
unused
type
is_ref__gc
❖ 現状:24byte消費(64bit環境)
zvalダイエット図解
❖ これで16byteにできないか?
_zvalue_value
refcount__gc
type
is_ref__gc
zvalダイエットの方針
❖ _zvalue_valueの「あまり」に参照カウンタを詰める!
❖ 参照カウンタを32bitから23bitに減らす!
❖ 浮いた9bitに変数型とis_ref_gcを詰める
zvalダイエットの実装
❖ _zval_structを構造体から共用体に変更!
❖ 詰めて使うための工夫!
❖ 基本的にzval関連のマクロを修正するだけでよい!
❖ Z_TYPE_P()とかZ_ADDREF_P()とか!
❖ 行儀の悪い箇所もあるので、チマチマ修正!
❖ https://github.com/hnw/php-src/tree/PHP-5.5.9-smallzval
zvalダイエットの結果
❖ make testが99%以上通る程度には動いた!!
❖ ベンチマークテストを動かしてみた!
❖ sizeof(zval)は24→16に減った!
❖ 性能面:ほぼ同じか少し遅い印象…!
❖ 消費メモリ量:zvalのサイズ減による寄与はわずか
❖ PHPのzval概要!
❖ zvalをダイエットする!
❖ さらなるダイエットへの道
失敗の原因を考える(1)
❖ 個別に速度測定!
❖ zvalのallocationは速くなった!
❖ zval同士の値のコピーは少し遅くなった!
❖ 参照カウンタへのアクセスも少し遅くなった!
❖ 仮説1:差し引きゼロ!
❖ データの持ち方が複雑すぎる?
失敗の原因を考える(2)
❖ そもそも、zvalはポインタ渡しされることが多い!
❖ zvalのallocationやcopyが少ないとすれば、

サイズ減をしても生きない!
❖ 仮説2:「変数のサイズ減=性能向上」がPHPでは成り
立たない
ポインタ渡しの功罪
❖ 「大きい構造体を値渡しするのはダメ」という常識!
❖ (少なくとも昔は)Cの入門書に書いてあった!
❖ いまやポインタ64bit時代!
❖ 参照渡しのコストが相対的に増大!
❖ PHPは値渡しを避けすぎて無駄なコストを払っている?!
❖ 64bit環境だとzvalはポインタサイズの高々3倍
Copy-on-write の功罪
❖ PHPの変数コピーは「Copy-on-write」!
❖ PHP上の代入・値渡し=実装上はポインタ渡し!
❖ 必要があるときだけコピーする!
❖ コピーは最低限になる!
❖ 変数の代入のたびに参照カウンタの増減が必要!
❖ 値渡しより、メモリへのwriteはむしろ増える?
まとめ(一部妄想)
❖ PHPのzvalをダイエットしたが効果は無かった!
❖ PHPの「Copy-on-write」は現代のCPUでは生きない?!
❖ 基本型を値渡しすれば高速化の道もあるのでは!
❖ (zval *)にNaN boxingでint/doubleを入れたい!
❖ zvalのまま使ってる場所の始末が非現実的かも…
ご静聴
ありがとう
ございました

zval をダイエットしてみた