SlideShare a Scribd company logo
A remarkable improvement on data structure in PHP7
PHP7の内部実装から学ぶ

性能改善テクニック
hnw
Developers Summit 2015 KANSAI
(2015/9/4)発表資料
自己紹介
❖ @hnw
❖ 勤務先:KLab株式会社
❖ カレーとバグが大好物
❖ PHP歴15年
❖ PHPや周辺エクステンションにバグレポ・PR多数
アジェンダ
❖ さいきんのPHP7
❖ PHP7の新機能
❖ PHP7って速いの?
❖ どこがボトルネック?
❖ PHP7のデータ構造
まずはアンケート
普段PHPを
書いている方?
実際にPHP7を

試したことがある方?
❖ さいきんのPHP7
❖ PHP7の新機能
❖ PHP7って速いの?
❖ どこがボトルネック?
❖ PHP7のデータ構造
PHP7?
❖ PHP 5.6.xの次のバージョンがPHP 7.0.0
❖ PHP6はスキップ
❖ 約10年ぶりのメジャーバージョンアップ
さいきんのPHP7
❖ 予定通りalphaバージョン、betaバージョンをリリース
❖ 現在最新はPHP 7.0.0 RC1
❖ 11月に正式版リリース予定
PHP7、どれくらい変わる?
❖ PHP7で採択されたRFCは48個
❖ 参考:PHP 5.6は17個、PHP 5.5は20個
→普段のマイナーバージョンアップよりは変化が大きい
PHPのRFCシステム
❖ 新機能の導入にはRFCと呼ばれる説明ページが必要
❖ 提案内容や変更による影響などを書く
❖ MLで議論後、投票によって採用・不採用が決まる
❖ 過半数または2/3以上の同意が必要(内容による)
❖ スピード感は無いが、十分機能している印象
❖ 議論を追うのに非常に便利
PHP7で変わらないこと
❖ PHPは言語仕様の変更に対して非常に保守的
❖ PHP7でも後方互換性は重視されている
❖ 移行コストは十分低いはず
❖ さいきんのPHP7
❖ PHP7の新機能
❖ PHP7って速いの?
❖ どこがボトルネック?
❖ PHP7のデータ構造
致命的エラーが例外になった
❖ PHP5までの致命的エラー
❖ エラーハンドリングできず、即座に終了していた
❖ PHP7の致命的エラー
❖ Errorという新しい例外になった
❖ トップレベルに到達すると今まで通りのエラーになる
→ユニットテストで致命的エラーから復帰できる
??演算子の新設
❖ nullでなければその値を、nullなら右オペランドを返す
❖ isset()と同様に未定義値に対しても使える
❖ ようやくisset()地獄から解放されるぞ!
無名クラスの導入
❖ クラス定義と同時にインスタンス化できる構文を導入
❖ その場限りのインスタンスを作りたいときに便利
AST(抽象構文木)の導入
❖ 解釈フェーズが

1段増えた
❖ 難しい文法が導入

できるようになった
❖ opcodeの最適化を
行うようになった
Zend VM
opcode
Parser
Lexer
token
PHP
Zend VM
opcode
Parser
Lexer
token
PHP
Opcode Compiler
AST
PHP 5 PHP 7
返り値のタイプヒントをサポート
❖ 関数の返り値に型が指定できるようになった
❖ 抽象クラスやインターフェースで指定すると便利
スカラ型のタイプヒントをサポート
❖ 以下の型が引数・返り値で指定できるようになった
❖ int型
❖ float型
❖ string型
❖ bool型
❖ 議論が続いていたが、ついに決着
非推奨だった機能を廃止
❖ PHP5.6までに非推奨になった機能をPHP7で廃止
❖ ereg関数(preg関数使ってね)
❖ mysql関数(mysqli関数かPDO使ってね)
❖ その他
アジェンダ
❖ さいきんのPHP7
❖ PHP7の新機能
❖ PHP7って速いの?
❖ どこがボトルネック?
❖ PHP7のデータ構造
PHP7は速いらしい
❖ 「PHP5より倍速い」
❖ 「HHVMとほぼ互角」
❖ ホントに?
PHP7の性能(1)
Zeevのブログ記事(2014/7)より
PHP7の性能(2)
DmitryのZendCon 2014での発表(2014/10)より
PHP7の性能(3)
RasmusのFluent 2015での発表(2015/4)より
PHP7の性能(4)
❖ 性能改善を積み重ねてきたことがわかる
速くなりすぎ?
❖ PHP7は確かに速い
❖ 約1年間で2倍の高速化(WordPressで比較)
❖ 今までも性能改善をサボっていたわけじゃない
❖ 過去10年間(PHP5.0→5.6)で2倍の高速化
❖ 5.4以降は頭打ちの感さえあった
何があったのか?
❖ メジャーバージョンアップならではの大手術
❖ 基本的なデータ構造の変更
❖ 高速化チームの裁量が大きかった
❖ RFC「Move the phpng branch into master」
❖ 様々なアイデアを約1年間に渡って実現
❖ さいきんのPHP7
❖ PHP7の新機能
❖ PHP7って速いの?
❖ どこがボトルネック?
❖ PHP7のデータ構造
性能改善に「銀の弾丸」はない
❖ 性能改善:ボトルネックを順につぶしていく作業
❖ 一発で改善、みたいなことは滅多にない
❖ ボトルネック以外の箇所をいくら改善してもムダ
❖ ボトルネックを見つけるところからスタート
PHP7チームが見つけたボトルネック
❖ PHP5でベンチマークテストしてみた
❖ メモリアロケーション・ハッシュ操作の時間 30%
❖ 内部関数の実行時間 30%
❖ VMの実行時間 30%
❖ その他 10%
PHP7チームが見つけたボトルネック
❖ PHP5でベンチマークテストしてみた
❖ メモリアロケーション・ハッシュ操作の時間 30%
❖ 内部関数の実行時間 30%
❖ VMの実行時間 30%
❖ その他 10%
この辺は今までチューニングしてきた
PHP7チームが見つけたボトルネック
❖ PHP5でベンチマークテストしてみた
❖ メモリアロケーション・ハッシュ操作の時間 30%
❖ 内部関数の実行時間 30%
❖ VMの実行時間 30%
❖ その他 10%
ほぼ手つかず、改善の余地ありそう
メモリ周りがボトルネック候補
❖ 具体的に何をすればいいのか?
❖ 普段のチューニングとは違う知識が必要
Q. 何がボトルネック?
❖ N次元行列の行列積の実行時間(C言語で記述)
行列積(擬似コード)
Q. 何がボトルネック?
❖ 急に5倍ほど遅くなっている
?
答: L3キャッシュミス
❖ 行列全体がキャッシュに乗り切らなくなった
→CPUコアは余裕があるのにメモリで待たされる
❖ 行列積の計算では同じ要素のreadがN回発生
❖ 2回目以降キャッシュヒットするかどうかの差
キャッシュの階層構造
L3 cache
L1 cache
L2 cache
CPU core
memory
L1 cache
L2 cache
CPU core
❖ 一般に、上位キャッシュほど速くて小さい
メモリの遅さ
❖ CPU 1クロックサイクル:0.3ns
❖ L1キャッシュヒット:1.2ns
❖ L2キャッシュヒット:3ns
❖ L3キャッシュヒット:12ns
❖ メモリアクセス:60-100ns
雑に言うと、メモリアクセス1回の間に演算300回できる
メモリの遅さ
❖ メモリアクセスはボトルネックになりやすい
❖ CPUに対して相対的に遅い
❖ キャッシュを有効に使えるかどうかが重要
❖ 上位キャッシュのヒット率アップ→性能改善
❖ 例:データ構造のムダを削る
キャッシュライン
❖ キャッシュは「キャッシュライン」単位で行われる
❖ キャッシュラインサイズ:64bytesなど
❖ 連続したメモリ領域が一度にキャッシュされる
❖ 同時に使うデータは近いメモリ領域に置くと有利
メモリ関連の最適化
❖ キャッシュヒット率を向上
❖ 可能な範囲でデータサイズを削る
❖ キャッシュラインを意識
❖ 同時に使うデータは近いメモリ領域に配置する
PHP7でも、このような最適化が行われている
❖ さいきんのPHP7
❖ PHP7の新機能
❖ PHP7って速いの?
❖ どこがボトルネック?
❖ PHP7のデータ構造
データ構造の変更
❖ PHP7では性能改善のためデータ構造を見直した
❖ PHPの変数(zval)
❖ 文字列(zend_string)
❖ 配列(HashTable / Bucket)
PHP5のzval(int型)
❖ 未使用領域が多い

(intの場合24bytes中10bytes)
❖ 必ずポインタ参照
❖ 参照カウンタを持つ、コピーオンライト
PHP7のzval(int型)
❖ 計16bytes
❖ 参照カウントしない、代入では常にコピー
PHP7のzvalへの評価
❖ メモリ上のムダな隙間を減らす変更
❖ ポインタ参照が1段減った
❖ int型・float型・bool型でコピーオンライト廃止
→サイズ削減によりキャッシュヒット率向上
PHP5のzval(string型)
❖ 文字列長と文字列本体が別の領域になっている
pointer
refcount type unusedis_ref
unused
pointer to zval
string length
string characters
zval
PHP7のzval(string型)
❖ 文字列長と文字列本体を連続領域に配置
❖ 参照カウントあり、同じ文字列は使い回す
string characters
flags
pointer
type reservedflags
refcount type gc_info
hash_value
string length
zend_string
zval
PHP7の文字列への評価
❖ 文字列長と文字列本体を連続領域に配置
→キャッシュラインを意識した変更
ねんがんの配列をてにいれたぞ!
❖ PHP7から、「本物の配列」が導入されました
!
「いままで配列なかったの?」
「はい」
配列とは
❖ (典型的には)連続するメモリ領域を確保する
❖ インデックスは0から連続する数字
❖ 読み書きが高速
リンゴ
バナナ
トマト
ニンジン
0
1
2
3
連想配列とは
❖ 文字列をキーにできる
❖ そこそこ読み書きも速い(配列よりは遅い)
"apple"
0
"banana" "carrot"
1 2 3 4 5 6 7
キーのハッシュ値を計算
"apple"=>リンゴ"banana"=>バナナ "tomato"=>トマト
"tomato"
"carrot"=>ニンジン
PHP5の配列
❖ PHP5までは連想配列しか無かった
❖ $array[1]でも連想配列アクセスしていた
❖ 配列より複雑な構造、キャッシュに優しくない
PHP5の配列
nTableSize
nNextFreeElement
HashTable
nTableMask
nNumOfElements
pListTail
arBuckets
unused
pInternalPointer
pListHead
pDestructor
nApp
lyCo
unt
persi
stent
bAppl
yProte
ction
unused
h
nKeyLength unused
pData
pDataPtr
pListNext
pListLast
pNext
pLast
arKey
Bucket
zval
pointer to Bucket
(Bucket *)[]
pointer to Bucket
pointer to Bucket
pointer to Bucket
Another(Bucket*)
whichisinthesamehashbin
PHP7の配列
❖ PHP7から配列と連想配列が別の構造になった
❖ 配列のときはハッシュテーブルが省略される
❖ 内部で自動的に選択される
❖ 配列・連想配列のデータは連続領域に置かれる
PHP7の配列
arData
flagsrefcount type gc_info
zend_array
u nTableMask
nNumUsed nNumOfElements
nTableSize nInternalPointer
nNextFreeElement
pDestructor
zval
Bucket[]
h (hash value)
key (キーへのポインタ)
h
key
0
1
h
key
2
zval
zval
❖ 他言語の配列と同じ構造

(数字キーのみの場合)
PHP7の配列への評価
❖ 配列と連想配列の区別ができた
❖ 配列のときのムダが減った
❖ データが連続領域に置かれるようになった
❖ 1要素あたりデータサイズ減少(72bytes→32bytes)
→キャッシュヒット率・キャッシュラインを意識した変更
PHP7の新データ構造
❖ 3つともキャッシュを意識したデータ構造
❖ 性能面でインパクトの大きい変更
❖ 「PHPなかなかやるじゃん」
PHP7の新データ構造
❖ 3つともキャッシュを意識したデータ構造
❖ 性能面でインパクトの大きい変更
❖ 「PHPなかなかやるじゃん」
❖ 他の言語では以前から採用されている
❖ Python、Ruby、JavaScriptエンジン
性能改善の面白さ
❖ 隣接領域の知識が役立つことがある
❖ 言語処理系ならCPUやメモリの知識
❖ Webアプリならミドルウェア・ネットワークの知識
まとめ
❖ PHP7は高速、PHP5.6の倍程度の性能
❖ オシャレな新機能を採用
❖ 現代のCPUに合わせたデータ構造変更を行った
❖ データサイズ削減(キャッシュヒット率向上)
❖ 連続領域にデータを配置(キャッシュラインを意識)
ご静聴
ありがとう
ございました

More Related Content

PHP7の内部実装から学ぶ性能改善テクニック