読者です 読者をやめる 読者になる 読者になる

WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

Facebookが公開した言語「Hack」の動作環境を構築

AWS Hack HHVM PHP

こんにちは、サーバー担当の山内です。
今回は、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  

hhvmリポジトリのmasterブランチから毎日深夜にビルドされる最新版 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;  
}