こんにちは、サーバー担当の山内です。
今回は、Facebookが公開したPHP互換の言語「Hack」が動作するnginxサーバーをAWSのEC2上に構築します。
1. Launch Your Instance!
Ubuntu Server 12.04 - ami-f381f5f2 (64-bit)を使います。
ポート22と80を開けておきます。
2. HHVMのインストール
sshで接続したら、PHPとHackの実行時コンパイラであるHHVMをインストールします。
今回は、HHVMの最新版※「HHVM Nightly Packages」を使うことにします。
$ sudo add-apt-repository ppa:mapnik/boost $ wget -O - http://dl.hhvm.com/conf/hhvm.gpg.key | sudo apt-key add - $ echo deb http://dl.hhvm.com/ubuntu precise main | sudo tee /etc/apt/sources.list.d/hhvm.list $ sudo apt-get update $ sudo apt-get install hhvm-nightly
Release Schedule
https://github.com/facebook/hhvm/wiki/Release-Schedule
3. nginxのインストール
次にnginxをインストールします。
HHVMが用意したインストールスクリプトを使った後、nginxの設定ファイルを変更します。
$ sudo apt-get install nginx chkconfig $ chkconfig nginx on $ sudo service nginx start $ sudo /usr/share/hhvm/install_fastcgi.sh
/etc/nginx/sites-available/defaultの25行目付近にindex.phpを追加します。
index index.php index.html index.htm;
nginxを再起動します。
$ sudo service nginx restart
4. HHVMの動作確認
hhvmコマンドでバージョン情報を確認します。
$ hhvm --version HipHop VM 3.1.0-dev+2014.04.21 (rel) Compiler: heads/master-0-g5bd61a62bcd9312362e65f0c1d92373d32a1eeaf Repo schema: bae411658aa8cdabaed4214a1eb5266f54ad4727
Hackの拡張子は.phpですが、開始タグは<?hhとなります。
example.php
<?hh phpinfo();
上記ソース(example.php)をhhvm example.phpで実行して、
HipHop
と表示されればHHVM環境の完成です。
5. Hello Hack!
HackとPHPの大きな違いは型注釈とコレクションです。コレクションはVector、Map、Set、Pairが使えます。従来のPHPからあるArrayは配列として使う他、ベクターやマップとして使うことも可能でした。HackでもPHP同様にArrayを扱えますが、HackのコレクションがArrayの代替として推奨されるようになりました。
Hack and HHVM: Goals - Manualによれば、次の4つの理由からコレクションを推奨しています。
- シンプルで直感的
- PHPのArrayと同等以上のパフォーマンス
- 静的型付け
- PHP5からの移行が容易
PHPのArrayは自由に使える反面、ソースの可読性とパフォーマンスが低下するのでコレクションを使ったほうが良いというものです。
6. ベンチマーク
実際にArray、Vector、Mapの実行速度を比較します。
各要素を3乗した結果をArrayやVector、Mapに追加していく処理を100万回ループさせ、それぞれの処理にかかった実行時間と使用メモリ量を取得します。
今回は、m1.medium上で下記1〜3のコードを/usr/share/nginx/wwwに設置し、ブラウザからアクセスしました。
1. Array
<?php function main() { // 開始時のメモリ使用量 $startMem = memory_get_usage(true); // 開始時間 $startTime = microtime(true); $array = [1, 2, 3]; $result = []; // 100万回ループ for ($i = 0; $i < 1000000; $i++) { // 各要素を3乗する $result[] = array_map(function($x) { return pow($x, 3); }, $array); } // 処理時間 echo 'time spent: ' . (microtime(true) - $startTime) . '[sec]' . '<br>'; // メモリ使用量 echo 'mem usage: ' . ((memory_get_usage(true) - $startMem) / (1024 * 1024)) . '[MB]'; } main();
2. Vector
<?hh function main(): void{ // 開始時のメモリ使用量 $startMem = memory_get_usage(true); // 開始時間 $startTime = microtime(true); $vector = Vector<int> {1, 2, 3}; $result = Vector<int> {}; // 100万回ループ for ($i = 0; $i < 1000000; $i++) { // 各要素を3乗する $result[] = $vector->map(function(int $x): int{ return pow($x, 3); }); } // 処理時間 echo 'time spent: ' . (microtime(true) - $startTime) . '[sec]' . '<br>'; // メモリ使用量 echo 'mem usage: ' . ((memory_get_usage(true) - $startMem) / (1024 * 1024)) . '[MB]'; } main();
3. Map
<?hh function main(): void{ // 開始時のメモリ使用量 $startMem = memory_get_usage(true); // 開始時間 $startTime = microtime(true); $map = Map {0 => 1, 1 => 2, 2 => 3}; $result = Map {}; // 100万回ループ for ($i = 0; $i < 1000000; $i++) { // 各要素を3乗する $result[$i] = $map->map(function(int $x): int{ return pow($x, 3); }); } // 処理時間 echo 'time spent: ' . (microtime(true) - $startTime) . '[sec]' . '<br>'; // メモリ使用量 echo 'mem usage: ' . ((memory_get_usage(true) - $startMem) / (1024 * 1024)) . '[MB]'; } main();
結果は次のとおりです。
実行時間 [sec] | 使用メモリ量 [MB] | |
---|---|---|
Array(HHVM) | 0.56768 | 89.03694 |
Vector | 1.12458 | 111.55560 |
Map | 1.34059 | 192.97272 |
Array(PHP-FPM)※ | 4.84081 | 687.50000 |
※PHP 5.5.9、Zend OPcache有効。
最速だったのはArray(HHVM)でした。次点のVectorと比較するとおよそ2倍高速、メモリ使用量はおよそ20%少なく、Array(PHP-FPM)との比較に至っては8〜9倍高速、メモリ使用量はおよそ85%少ない結果となりました。
Hack and HHVM: Goals - Manualにあるとおり、HackのコレクションはPHPのArray以上のパフォーマンスを発揮するようです。
7. Tips
PHP-FPM上で動かす際、デフォルト設定のままだと「PHP Fatal error: Allowed memory size of xxx」エラーで動かなかったので、/etc/php5/fpm/php.iniのmemory_limitを32MBから1024MBに変更しました。(service php5-fpm restartで設定を反映。)
/etc/php5/fpm/php.ini
; Maximum amount of memory a script may consume (128MB) ; http://php.net/memory-limit memory_limit = 1024MB
また、nginxの「upstream timed out」エラーを回避するためhttpブロックにsend_timeout 300;を追記しました。
/etc/nginx/nginx.conf
http { ## # Basic Settings ## send_timeout 300; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; # server_tokens off;
同エラーを回避するためさらに、location ~ .php$ブロックにfastcgi_read_timeout 300;を追記しました。(service nginx restartで設定を反映。)
/etc/nginx/sites-available/default
location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini # With php5-cgi alone: # fastcgi_pass 127.0.0.1:9000; # With php5-fpm: fastcgi_pass unix:/var/run/php5-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_read_timeout 300; }