Hatena::ブログ(Diary)

hnwの日記 このページをアンテナに追加 RSSフィード

[プロフィール]
 

2014年12月16日(火) 整数型のコピーオンライトは本当に遅いのか実験してみた このエントリーを含むブックマーク このエントリーのブックマークコメント

前回記事「PHP7はなぜ速いのか(zval編)」ではPHP7から内部的なデータ構造が大きく変わるという話題に触れました。具体的には、PHP5では全ての型の変数についてコピーオンライト方式を採用していたこと、この方式はデータサイズが大きいときに有効であること、またPHP7から真偽値型・整数型・浮動小数点数型の変数についてコピーオンライトをやめ、即値コピーするようになったことなどを紹介しました。


本稿ではデータサイズが小さいときのコピーオンライトが本当に非効率なのかどうか、PHP5とPHP7で実験してみます。


変数に別の変数を1億回代入してみる

まずは、次のプログラムを実行してみましょう。


<?php
function foo() {
    $x = 1;
    $a0 = $x;
    $a1 = $x;
    /*(略)*/
    $a99998 = $x;
    $a99999 = $x;
}
$t = microtime(true);
for ($i = 0; $i < 1000; $i++){
    foo();
}
var_dump(microtime(true)-$t);

上のプログラムで「(略)」となっている部分は10万行弱が省略されています。これは、関数fooの中で10万個の異なる変数に$xを代入し、この関数を1000回呼び出すのにかかった時間を測定するプログラムです。これをPHP 5.6.3とPHP 7のmasterブランチ最新版とで比較してみると、次のような結果になりました。

続きを読む

トラックバック - http://d.hatena.ne.jp/hnw/20141216

2014年12月7日(日) PHP7はなぜ速いのか(zval編) このエントリーを含むブックマーク このエントリーのブックマークコメント

この記事はPHP Advent Calendar 2014の7日目です。


僕は先日のPHPカンファレンス2014でPHP7に関するLTをしました(参照:「PHPNGの動向」)。ただ、時間が限られていたこともあり、あまり踏み込んだ内容には触れませんでした。


本稿ではLTの内容から深掘りし、zvalという内部的な構造体がどう変わるのか、性能面のメリットにフォーカスして解説してみます。


PHPをとりまく状況

まず最近のPHPの話題をおさらいしておきましょう。


これまでPHPには実用レベルの別実装が存在しない状態が続いていましたが、HHVMの登場で状況が変わってきました。HHVMはFacebookが開発しているPHP実行環境で、PHPより高速で互換性も高いのが特徴です。Facebookで実際に利用されているだけでなく、他の企業でも商用サービスで利用する事例が増えてきています。


そんな中、2014年のはじめ頃にPHPの性能改善プロジェクトPHPNGがスタートしました。これはPHPコミッターでありZend Technologies社員でもあるDmitry Stogovによるもので、PHP内部のデータ構造の変更をすることでPHPの言語仕様を変えずに性能改善を行うものです。このプロジェクトは極めて順調に進み、2014年8月にはPHP7のベースプロジェクトとして採用されました。


説明が前後してしまいますが、PHPのバージョニングについても最近動きがありました。現時点でのPHPの最新版は5.6.3ですが、次のメジャーバージョンアップで7.0になることが決まっています(参照:「PHP RFC: Name of Next Release of PHP」)。PHP7は2015年11月のリリースを目指しており、2015年6月にはコードフリーズしてRC1が出る予定です(参照:「PHP RFC: PHP 7.0 timeline」)。


ちなみに、PHP7での性能改善はかなり進んでいます。下記グラフはZendCon 2014での発表からの引用ですが、WordPressベンチマークテストしたときにPHP5.6の約1.7倍、HHVMに匹敵する性能となっています。


f:id:hnw:20141206165304p:image

(「PHPNG a New Core for PHP7」より)


PHP7でzvalが変わった

ここから本題です。既に紹介しましたが、PHP7では内部的なデータ構造が変更されており、これが性能改善に貢献しています。


中でも、zval構造体の変更はかなり大胆な内容だと言えます。zvalは全てのPHP変数を管理するためのデータ構造であり、今回の変更はPHPソースコード全体に影響するような大変更になっています。ここに注目したこと、これをやりきったことはかなりのhackだと思います。


以下、このzval構造体の変更内容について図を交えて説明していきます。

続きを読む

2014年11月22日(土) PHP 5.5.0でイースターエッグが廃止されていた このエントリーを含むブックマーク このエントリーのブックマークコメント

PHPユーザーの皆様に悲しい(?)お知らせです。PHPのオシャレ機能として有名なイースターエッグPHP 5.5.0で廃止されていました。


そもそもPHPイースターエッグって何よ?という方のために説明しておくと、PHP 5.4までは任意のPHPページのURL末尾に特定文字列をつけると下記の象の画像や作者クレジットが表示されるような謎機能がありました*1


f:id:hnw:20141122184442g:image


詳しくは「PHP Easter Egg」をご覧ください。


ところが、PHP 5.5.0からこれが廃止されています。ChangeLogにはこれをほのめかすような内容が見つかりますが、下記の一文だけでピンとくる人も少ないでしょうから、ひっそり廃止されたという印象です。


Removed legacy features:

Remove php_logo_guid(), php_egg_logo_guid(), php_real_logo_guid(), zend_logo_guid()


PHP: PHP 5 ChangeLog


このイースターエッグが廃止された真相は僕にはわかりませんが、中の人にマジメな人がいたのかもしれませんね。

*1:この機能は設定で「expose_php = off」すれば無効になります。念のため

トラックバック - http://d.hatena.ne.jp/hnw/20141122

2014年11月15日(土) HHVM 3.3.1とPHP 5.6.2の==の違いを調べてみた このエントリーを含むブックマーク このエントリーのブックマークコメント

(11/15 16:15追記)本稿で指摘している違いの1件目についてバグレポを書いたところ、すぐ直すよーとのことでした。

(11/18 17:30追記)上記修正がmasterブランチに取り込まれていることを確認しました。


PHPJITコンパイラ実装であるHHVMが最近話題ですね。本家より高速というふれこみですし、Facebookが開発・実運用している実績もあるわけですから、導入を検討されている方も多いのではないでしょうか。


とはいえ、特に商用環境に導入するとなると互換性がどこまで確保できているかも重要です。現時点でも実運用に耐える程度の互換性はあるはずですが、僕は非常に保守的な人間なので、HHVMが互換性にどこまでこだわっているのかに興味があります。


今回、==演算子に絞ってHHVMとPHPの挙動を調べてみたところ、2点の違いを見つけたので紹介します。比較にはUbuntu14.04上のprebuilt版HHVM 3.3.1と自前ビルドしたPHP 5.6.2を利用しました。


浮動小数点数と16進数値文字列の比較が浮動小数点比較されない

PHPで数値と数値文字列を==で比較した場合は数値として比較されます。HHVMでも大半の場合は同じ動作になるのですが、浮動小数点数と16進数値文字列の組み合わせに限って16進文字列が0.0として比較されるようです。


<?php
var_dump(1.0 == "0x1"); // HHVM: false, PHP: true
var_dump(0.0 == "0x1"); // HHVM: true, PHP: false

これはHHVMのバグだと思われます。==処理の分岐が複雑すぎて間違えたんでしょうか。


整数の範囲を超えた10進数値文字列浮動小数点数として数値比較される

PHPで数値文字列同士を==で比較するときは原則として数値比較されるのですが、約2年前の修正から数値比較で精度が落ちる場合には文字列比較するようになりました(参考:「PHP 5.4.4から==の挙動が一段と難しくなりました - hnwの日記」)。しかし、HHVMはこの変更に追従していないようです。


これは次のようなコードで確認できます。


<?php
var_dump("9223372036854775807"=="9223372036854775808"); // HHVM: true, PHP: false

HHVMの挙動は少し前のバージョンのPHPと同じですから、実用上はそれほど大きい問題ではないでしょう。しかし、これはPHPの全変更に追従し続けるのは無理という証拠だとも言えそうです。特にPHPの場合はChangeLogにも残らずドキュメント化もされないような細かい変更が珍しくないので、全変更を把握することさえ非現実的かもしれません。


HHVMはPHPのCソースコードをかなり利用している


僕が見つけられた違いは上記の2点だけでした。関連する仕様の複雑さを考えるとありえないくらい同じだという印象です。


この再現性の高さはHHVMの中の人が頑張った成果かもしれませんが、PHPソースコードをうまく取り込んでいるおかげもありそうです。たとえば、HHVMのhphp/runtime/base/zend-strtod.cppを見ると、PHPのZend/zend_strtod.cをベースにしているのが明らかです。ソースコードを流用してしまえば、ドキュメント化されていないような仕様であっても再現できますし、何より楽ができそうで良い手ですね。


まとめ

  • HHVMとPHPの==に違いがあるかどうか調べた
    • 2点の違いを見つけたものの、十分互換性があると言えそう
    • とはいえ、PHPの細かい修正すべてに追従できているわけでもなさそう
  • HHVMはPHPのCソースコードをかなり流用している
    • PHPとの互換性が高い理由かもしれない

y-utiy-uti 2014/11/16 17:25 二件目は、つい最近 (下記コミットで) 修正されたようです。掲示のコードは master ブランチの HHVM では PHP と同様に false になりました。
https://github.com/facebook/hhvm/commit/1fd8ceb

hnwhnw 2014/11/16 17:41 なるほど、ありがとうございます。こんなことを気にするのは僕くらいかと思ってたんですが、想像よりも最新のPHPの挙動に合わせてるんですね。

2014年11月9日(日) APCuは速いけど初期設定がイマイチだというお話 このエントリーを含むブックマーク このエントリーのブックマークコメント

ISUCON本戦で惨敗してきた皆さんこんにちは。昨日のやけ酒は最高でしたね!今日はISUCON予選のときに気づいたAPCuのイマイチな点を紹介します。


APCuというのはPHP extensionで実装されているKVS(Key Value Store)で、localhost内でのデータキャッシュに利用されます。かなり高速な上、APCの時代から考えると利用実績も十分あるため、PHPでは定番extensionの一つといえるでしょう。


ところで、APCuに大量のデータ(10万エントリ以上)を格納するとデータの取得や更新が遅くなることがあります。APCuでは格納するエントリ数の「ヒント」をあらかじめ設定値で指定するようなつくりになっているのですが、この設定があまり知られておらず、デフォルト値もかなり小さいため、遅いまま使っている環境が多いように推測しています。


APCuの設定

APCuの設定値に、「apc.entries_hint」というものがあります。これはAPCの「apc.user_entries_hint」と同じもので、APCuに格納するデータ総数の推測値を指定するものです。


apc.entries_hint


A "hint" about the number variables expected in the cache. Set to zero or omit if you're not sure. (Default: 4096)


APCu INSTALL


この「apc.entries_hint」の指定を元に、APCuがデータを格納するときに使うハッシュのスロット数が決定されます。また、このスロット数はエントリ数が増えても拡張されることはありません。同じハッシュスロットに格納されているデータはlinked listで格納されるため、データが増えれば増えるほどlinked listが長くなります。


apc.entries_hintの初期値は4096であるため、たとえばAPCuに40万エントリを格納した場合、linked list部分の長さが平均で100になってしまいます。データの参照や更新のたびにハッシュ値の計算に加えてキーの重複チェックのため100エントリ分の文字列比較が必要になるため、速度面で効率が悪くなってしまいます*1


apc.entries_hintにデータの総数を指定しておけばlinked list部分の長さが平均1になり、速度とメモリ効率の観点から理想に近づくと言えます。多少ブレても影響は軽微なので、若干小さめの値を指定するくらいのノリで良いと思います。


APCu 4.0.6までの制約

ところで、APCu 4.0.6まではapc.entries_hintにどれだけ大きい値を指定しようと、ハッシュスロット数は19457までに制限されていました*2


この制約はデータを20000個前後しか格納しない前提であれば問題ないのですが、現実にはもっと大量のデータを格納することがあるはずです。そこで、下記のようなPull Requestを投げ、より大きい設定値に対応できるようにしました。



これは無事採用され、APCu 4.0.7からはハッシュスロット数の最大値が983063になっています。


apc.entries_hintを大きくしたときのメモリ消費量

このスロット数が1増えるたびに、64bit環境では64バイトのメモリを消費します。つまり、2万スロットであれば約1.2MBのメモリを消費します。100万スロットでも約64MBです。大抵の環境では、hintを大きくするリスクは小さいと言えそうです。


もっとも、これ以外に格納するデータに対応する分のメモリも必要です。APC/APCuでは1エントリごとに管理用の構造体で960Byteを消費します。更にキーと値に対応するデータの分もメモリを消費しますので、数百万エントリを格納する前提の場合は十分なメモリを用意しておきましょう。


現在使っているエントリ数を確認する方法

APCuに付属しているapc.phpでも表示されますし、下記のようなスクリプトでも確認できます。


<?php
$ret=apc_cache_info('user', true);
var_dump($ret['nentries']); /* 現在使っているエントリ数 */

今回の話に関わらず、この数字が単調増加していないかどうか普段からモニタリングしておいた方が良いでしょう。


まとめ

  • APCuの設定値apc.entries_hintに格納エントリ数の推定値を指定すると性能が上がる
  • APCu 4.0.6以下ではapc.entries_hintに2万以上の値を指定しても無視される
    • 4.0.7から100万付近が上限になった

補足しておくと、このapc.entries_hintの修正が必須になるような環境はかなり珍しいかと思います。上にも書いた通り、多少遅くなったとしてもAPCuは十分高速なので、他のボトルネックに比べれば無視できるはずです。ただ、APCuに数百万オーダーのデータを格納している場合は試しに設定変更してみる価値があると思います。

*1:効率が悪いといっても、この程度ならlocalhostmemcachedを使うよりは高速です。同一プロセス内で処理が完結するのは速度面で有利なのです

*2ハッシュ値modを取ったときの偏りを減らすため、hintより大きい素数をリストから探すような仕組みになっています

トラックバック - http://d.hatena.ne.jp/hnw/20141109
 
ページビュー
1297959