Y's note

Web技術・プロダクトマネジメント・そして経営について

本ブログの更新を停止しており、今後は下記Noteに記載していきます。
https://note.com/yutakikuchi/

defineを辞めてhidefを使う

追記

PHPでdefineのかわりにhidefをつかう必要はない - id:k-z-h はてなブックマーク - PHPでdefineのかわりにhidefをつかう必要はない - id:k-z-h
トラックバックに対して反応を書いた事が無いんですが、ちゃんとした内容が掲載されていたのでこちらでも追記しておきます。エントリーアップ時にdefineを辞めてhidefに切り替えることを強く推薦したつもりは全くありませんでした。確かに僕が書いた「まとめ」の項目だけを見るとそう捉えれる事もできるのでまとめの項目を「hidefの導入を検討しても良いと思います」にしました。kazuhaさんが仰られているY!社の現状予想と僕の読解力の無さの話は置いておいて(笑)、その他defineの改善効果とPECLの話はご指摘通りかなと思いました。カンファレンス当日の発表の一部を深堀したつもりだったんですが、問題の本質に誤解を与えてしまう内容を書いた事は反省します。

その他の方からdefineとhidef以外でオブジェクト定数(const)でもいいじゃんという話もコメントに頂いてまして、パフォーマンスの検証とかしてないですけどその方法もありかなと思いました。

PHPカンファレンス2013に参加してきました

PHPカンファレンス2013 はてなブックマーク - PHPカンファレンス2013
2013/9/14(土)に開かれたPHPカンファレンス2013に参加してきました。主催、運営、スピーカーを担当された方々、大変お疲れ様でした。全体的には大半の人が知っている基礎的な内容が多かったと思います。スピーカーの方々も本当はもっとコアな話がしたいんだろうなぁとか、でも難しい話をし始めるとみんな分からなくなるんだろうなぁと思いながら聞いていました。個人的にセキュリティ面の知識が不足しているという事もあって、以下の2タイトルがとても勉強になりました。

前職の先輩方、大学の後輩も積極的にスピーカーを担当していて凄いなぁと感心していました。PHPを2年以上書いていない僕もどこかで間違いの無い知識を発表してみようかなぁと思ったり。(笑)今日は前職の先輩がPHPのhidefについてさらりと触れられていた内容について僕の方でも追加で紹介したいと思います。

defineでは無くhidefを使う

PECL :: Package :: hidef はてなブックマーク - PECL :: Package :: hidef
カンファレンスの説明でdefineはスクリプト実行毎に呼ばれるので、起動時一度定数読み込みするためにPHPExtensionを使うのが良いって説明もありました。ただしExtensionは開発コストが大きいのでPECLのhidefを使う話をします。

PHPCentOSのversion

今回僕が実行した環境です。PHPのversionは5.4.19、CentOSは6.4になります。

$ php -v
PHP 5.4.19 (cli) (built: Aug 22 2013 08:03:53) 
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies

$ less /etc/redhat-release
CentOS release 6.4 (Final)
defineが遅いという話

PHP: apc_define_constants - Manual はてなブックマーク - PHP: apc_define_constants - Manual
apc_define_constantsの項目にもdefineが遅いという記述が書いてあります。以下は引用です。

ご存知のとおり、 define() は非常に遅いです。 APC を使用する主な利点はスクリプト/アプリケーションのパフォーマンスの改善なので、 大量の定数を定義する手順を合理化するために、この仕組みが提供されています。 しかし、この関数は期待通りの動作をしません。よりよい解決策として、PECL の » hidef 拡張モジュールを試してみましょう

hidefの設定

yumphp-pearを、peclでhidefをinstallします。またphpのiniファイルにhidefの設定を読み込む記述を追加します。

$ sudo yum install php-pear -y
$ sudo pecl install hidef
()
Build process completed successfully
Installing '/usr/lib64/php/modules/hidef.so'
install ok: channel://pecl.php.net/hidef-0.1.13
configuration option "php_ini" is not set to php.ini location
You should add "extension=hidef.so" to php.ini

$ sudo vim /etc/php.d/hidef.ini
[hidef]
; add 3lines
extension=hidef.so
hidef.ini_path=/var/php/hidef/
hidef.data_path=/var/php/hidef/
hidefの設定ファイルを置く

上で設定したhidef.ini_pathに対してXXX.iniファイルを設置します。ここではphpcon.iniという名前で配置します。また設置したhidefファイルが有効化されたかどうかを実行してみます。

$ sudo vim /var/php/hidef/phpcon.ini
; int型
int PHPCON = 2013;
; string型
str PHPCONFERENCE = "いいね!";

$ php -r "echo PHPCON;"
2013
$ php -r "echo PHPCONFERENCE;"
いいね!

注意点としてhidefを設定するとグローバルな定数設定になるので、影響度合いを考えて設定してください。当然hidefで設定したdefineをスクリプトから設定を上書きする事は出来ません。

<?php

define( 'PHPCON', 2013 );
define( 'PHPCONFERENCE', 'いいね!' );
echo PHPCON . ' ' . PHPCONFERENCE;

// 実行結果
PHP Notice:  Constant PHPCON already defined in /home/yuta/work/php/hidef.php on line 3
PHP Notice:  Constant PHPCONFERENCE already defined in /home/yuta/work/php/hidef.php on line 4
2013 いいね!
define vs hidefのscript performance

defineとhidefのscriptパフォーマンスを比較してみます。定数化した値を参照するだけのコードそれぞれをphpファイルに落とし込んでシェルを介して10000回実行してみます。

<?php

define( 'HIDEF_PHPCON', 2013 );
define( 'HIDEF_PHPCONFERENCE', 'いいね!' );
$phpcon = HIDEF_PHPCON;
$phpconference = HIDEF_PHPCONFERENCE;
<?php

$phpcon = PHPCON;
$phpconference = PHPCONFERENCE;
#!/bin/sh
file=$1
for i in {1..10000}; do
   /usr/bin/php $file 
done;
種別 timeコマンド結果
define ./exec.sh define.php 328.66s user 332.10s system 71% cpu 15:30.03 total
hidef ./exec.sh hidef.php 374.17s user 397.88s system 76% cpu 16:49.28 total

あらら、2行の定数化scriptではhidefを使った方がperformanceが悪い結果になってしまいました...これは憶測ですがphpの起動を含めた実行計測だとhidefの読み込みに処理コストが掛かっているようです。次はscriptの実行コストを計測するようにphpファイルにmicrotimeを仕込みます。下のscriptを100回実行して1回の実行の平均値を取ってみるようにします。

<?php

$start = microtime(true);
$phpcon = PHPCON;
$phpconference = PHPCONFERENCE;
echo microtime(true) - $start . "\n";
種別 平均実行時間
define 0.000384sec
hidef 0.000315sec

そうするとhidefの方が1.2倍速い事が分かりました。

define vs hidefをapache経由のperformance

今度はapacheを経由して実行してみます。初めにapache経由でhidefを使用したい場合は/var/php以下にhidef用のiniファイルを設置すると動作しなかったので、/var/www/php/hidefというディレクトリに設定を変更しました。

$ sudo vim /etc/php.d/hidef.ini
[hidef]
; add 3lines
extension=hidef.so
hidef.ini_path=/var/www/php/hidef/
hidef.data_path=/var/www/php/hidef/

/var/www/html以下に上で使用したdefine.phpとhidef.phpを設置してabスクリプトでperformanceを見てみます。

$ ab -n 10000 -c 10 http://127.0.0.1/define.php

Document Path:          /define.php
Document Length:        17 bytes

Concurrency Level:      10
Time taken for tests:   57.315 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      2100840 bytes
HTML transferred:       170068 bytes
Requests per second:    174.47 [#/sec] (mean)
Time per request:       57.315 [ms] (mean)
Time per request:       5.732 [ms] (mean, across all concurrent requests)
Transfer rate:          35.80 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0   23  11.4     22     152
Processing:     4   33  21.8     29     592
Waiting:        0   25  18.9     22     517
Total:         15   56  24.8     50     605
$ ab -n 10000 -c 10 http://127.0.0.1/hidef.php

Document Path:          /hidef.php
Document Length:        17 bytes

Concurrency Level:      10
Time taken for tests:   48.339 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      2100840 bytes
HTML transferred:       170068 bytes
Requests per second:    206.87 [#/sec] (mean)
Time per request:       48.339 [ms] (mean)
Time per request:       4.834 [ms] (mean, across all concurrent requests)
Transfer rate:          42.44 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0   20   8.1     20     118
Processing:     5   27  12.9     26     319
Waiting:        0   21  11.6     20     310
Total:          5   48  13.1     45     344

Percentage of the requests served within a certain time (ms)
  50%     45
  66%     48
  75%     50
  80%     51
  90%     56
  95%     63
  98%     76
  99%     94
 100%    344 (longest request)
種別 実行時間 rps
define 57.315sec 174.47
hidef 48.339sec 206.87

Apacheを経由した実行の場合、hidefの方が1.18倍速い事が分かりました。

xhprofによる検証

xhprofを入れてprofile検証をしてみます。まずはpeclからのinstallとiniファイルの設定、xhprofのwebviewプログラムをDocumentRootにコピーします。更にprofileした結果を画像で出力するgraphvizをinstallします。

$ sudo pecl install xhprof-0.9.3

$ sudo vim /etc/php.d/xhprof.ini
[xhprof]
; add 2lines
extension=xhprof.so
xhprof.output_dir=/var/www/xhprof

$ sudo cp -R /usr/share/pear/xhprof_* /var/www/html
$ ls /var/www/html
drwxr-xr-x. 2 root root 4.0K  915 20:56 2013 xhprof_html
drwxr-xr-x. 4 root root 4.0K  915 20:56 2013 xhprof_lib

$ sudo yum --enablerepo=remi install graphviz graphviz-gd -y

profileを参照するためのコードをdefine.phpとhidef.phpに埋め込みます。以下ではhidef.phpのコードを記載します。単純にhidefのコードを呼び出す前にxhprof_enable();で開始し、xhprof_disable();でprofileの収集を辞めて、実行したidをprofileページに渡すだけです。

<?php

xhprof_enable();

$phpcon = PHPCON;
$phpconference = PHPCONFERENCE;

$xhprof_data = xhprof_disable();

$XHPROF_ROOT        = '/var/www/html/';
$XHPROF_SOURCE_NAME = 'hidef.php';
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php";
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php";

$xhprof_runs = new XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($xhprof_data, $XHPROF_SOURCE_NAME);

echo "<a href='/xhprof_html/index.php?run=$run_id&source=$XHPROF_SOURCE_NAME'>xhprof Result</a>";

aタグでリンクを定義した/xhprof_html/index.phpに結果を渡すと、callgraphを見る事ができます。凄く単純な処理しか書いていないので、このケースにおいてはあまり重宝されないかもしれないですが、複雑なロジックの際にはボトルネックを特定するために見てみると良いと思います。defineの場合は実行に0.744ms、hidefは0.357msなので、倍近くhidefの方が速いという事が分かります。

種別 実行時間
define 0.744ms
hidef 0.357ms

まとめ

defineとhidefの違いを意識して導入を検討しても良いと思います。※まとめの表現を少し変えました。(2013/9/19)