(本稿はPHP Advent Calendar 2016の6日目であると同時に、「PHP7調査」と題した一連の記事の一つでもあります。)
はじめに
ちょうど1年前にリリースされたPHP 7.0では性能向上のための工夫が数多く行われました。そうした工夫の多くはPHP 7リリース前後に色々と宣伝されていたので、いまさら改めてお伝えすることもないでしょう。その一方で、いまだにほとんど知られていない性能改善ポイントもあるようです。たとえば以下もその一つでしょう。
Global register variables with gcc 4.8+
上記はPHP 7の性能改善の詳細を紹介するRasmusさんのプレゼン資料からの引用です。これによれば、PHP 7では「Global register variables」を使って高速化を達成しているらしいのですが、いったい何のことでしょうか?
「Global register variables」というのはGCCやその他一部コンパイラの持つ機能で、CPUの特定のレジスタをグローバル変数に明示的に割り当てる機能です。言うなればC言語のregister
キーワードの強力版といった感じでしょうか。詳しくは「Global Register Variables - Using the GNU Compiler Collection (GCC)」などをご確認ください。
上記マニュアルにも書いてあるように、一般論としてはレジスタの割り当てを人間が考えるよりもコンパイラに判断してもらった方が良いコンパイル結果になりやすいはずです。一方で、プログラミング言語のインタプリタなどグローバル変数に頻繁にアクセスする場合はこの機能が有効だと考えられます。
PHPのソースコード中でのGlobal register variablesの使われ方
さて、その機能はPHPソースコード中のどこで使われているのでしょうか。以下にGlobal register variablesに関係する部分を抜粋してみました。
#ifdef HAVE_GCC_GLOBAL_REGS
# if defined(__GNUC__) && ZEND_GCC_VERSION >= 4008 && defined(i386)
# define ZEND_VM_FP_GLOBAL_REG "%esi"
# define ZEND_VM_IP_GLOBAL_REG "%edi"
# elif defined(__GNUC__) && ZEND_GCC_VERSION >= 4008 && defined(__x86_64__)
# define ZEND_VM_FP_GLOBAL_REG "%r14"
# define ZEND_VM_IP_GLOBAL_REG "%r15"
# elif defined(__GNUC__) && ZEND_GCC_VERSION >= 4008 && defined(__powerpc64__)
# define ZEND_VM_FP_GLOBAL_REG "r28"
# define ZEND_VM_IP_GLOBAL_REG "r29"
# elif defined(__IBMC__) && ZEND_GCC_VERSION >= 4002 && defined(__powerpc64__)
# define ZEND_VM_FP_GLOBAL_REG "r28"
# define ZEND_VM_IP_GLOBAL_REG "r29"
# endif
#endif
#ifdef ZEND_VM_FP_GLOBAL_REG
#pragma GCC diagnostic ignored "-Wvolatile-register-var"
register zend_execute_data* volatile execute_data __asm__(ZEND_VM_FP_GLOBAL_REG);
#pragma GCC diagnostic warning "-Wvolatile-register-var"
#endif
#ifdef ZEND_VM_IP_GLOBAL_REG
#pragma GCC diagnostic ignored "-Wvolatile-register-var"
register const zend_op* volatile opline __asm__(ZEND_VM_IP_GLOBAL_REG);
#pragma GCC diagnostic warning "-Wvolatile-register-var"
#endif
上記コードによれば、GCC 4.8以降と特定アーキテクチャ(i386/x86_64/powerpc64)の組み合わせでGlobal register variablesが使われるようです。また、IBMの商用Cコンパイラとpowerpc64の組み合わせでも有効なようです。
実際にレジスタに割り当てられる変数は2つあります。execute_data
は現在実行中の関数の情報(関数のローカル変数など)を格納する構造体へのポインタで、opline
は現在実行中のVM命令1個に対応する構造体へのポインタです。
これをレジスタに割り当てるのがそこまで有利かなあ?という気もしますが、laruenceさん(PHP 7の高速化に取り組んでいたPHPコミッターの一人)によれば5%の性能向上があったとのことです。
補足1:Rubyでのregister variables
ついでにRubyのソースコードを眺めてみましたが、非常に似たコードを発見しました。
#if VMDEBUG > 0
#define DECL_SC_REG(type, r, reg) register type reg_##r
#elif defined(__GNUC__) && defined(__x86_64__)
#define DECL_SC_REG(type, r, reg) register type reg_##r __asm__("r" reg)
#elif defined(__GNUC__) && defined(__i386__)
#define DECL_SC_REG(type, r, reg) register type reg_##r __asm__("e" reg)
#elif defined(__GNUC__) && defined(__powerpc64__)
#define DECL_SC_REG(type, r, reg) register type reg_##r __asm__("r" reg)
#else
#define DECL_SC_REG(type, r, reg) register type reg_##r
#endif
/* #define DECL_SC_REG(r, reg) VALUE reg_##r */
#if defined(__GNUC__) && defined(__i386__)
DECL_SC_REG(const VALUE *, pc, "di");
DECL_SC_REG(rb_control_frame_t *, cfp, "si");
#define USE_MACHINE_REGS 1
#elif defined(__GNUC__) && defined(__x86_64__)
DECL_SC_REG(const VALUE *, pc, "14");
# if defined(__native_client__)
DECL_SC_REG(rb_control_frame_t *, cfp, "13");
# else
DECL_SC_REG(rb_control_frame_t *, cfp, "15");
# endif
#define USE_MACHINE_REGS 1
#elif defined(__GNUC__) && defined(__powerpc64__)
DECL_SC_REG(const VALUE *, pc, "14");
DECL_SC_REG(rb_control_frame_t *, cfp, "15");
#define USE_MACHINE_REGS 1
#else
register rb_control_frame_t *reg_cfp;
const VALUE *reg_pc;
#endif
GCCとi386,x86_64,powerpcの組み合わせで変数をレジスタに割り当てています。内容としても、レジスタ上に確保している変数reg_pc
とreg_cfp
の用途はPHPと似た内容に見えますね。
ただし、Rubyの場合はvm_exec_core()
のローカル変数にレジスタを割り当てる形になっています。これの有利不利を判断する材料を筆者は持っていませんが、実装上の都合による差なんだろうと想像しています(PHPの場合はVM実行のメインループ中で関数呼び出しが頻繁に走るのでグローバル変数を使わざるを得ない、Rubyは同じ部分がgotoで実現されてるからローカル変数で良い、とかかな…?でもわざわざ明示的に指定しなくてもコンパイラが勝手にレジスタに割り当てそうな気もしますよね)
補足2:Clangについて
さいきんのMacOSXのコンパイラはClangが採用されています。つまり、MacOSX+PHP 7だとGlobal register variablesは使われていません。実際、MacOSX環境のconfig.log
を見ると次のようにGlobal register variablesが使えない環境として判定されています。
configure:5716: checking for global register variables support
configure:5764: cc -c -O3 -fvisibility=hidden -no-cpp-precomp conftest.c >&5
conftest.c:44:3: error: "global register variables are not supported"
一方でClang自体はGlobal register variablesをサポートしているように見えます。僅かな修正でMacOSX上のPHPやRubyがもっと速くなるかもしれませんね。
さいごに
PHP Advent CalendarなのにPHPのコードが1行も出てこない話題でごめんなさい。とはいえ、このようにPHP自体について調べていたつもりが、いつの間にかC言語レイヤに踏み込んでいることもあるわけです。
ちなみに、筆者は闇PHP勉強会というPHPのC言語レイヤの話が多めの勉強会を主催しております。こういう話も面白そうだな、と思われた方はぜひ参加をご検討ください。次回の第七回闇PHP勉強会はおかげさまで満員となっておりますが、名前だけでも覚えておいて頂ければ幸いです。