y_uti のブログ

統計、機械学習、自然言語処理などに興味を持つエンジニアの技術ブログです

Hack と PHP の実行速度の比較

Facebook によって開発されたプログラミング言語 Hack を試してみました。公式サイトはこちらです。

まず、手元の環境に Hack をインストールします。今回は、新たに Ubuntu Server 13.10 の環境を作成してインストールすることにしました。下記のウェブページにある "For Ubuntu 13.10:" の記載のとおりに特に問題なくインストールできました。

最初に Hello world を試してみます。以下のような hello.hh を作成します。普通の PHP のコードを書いて先頭を <?hh に変更するだけです。

<?hh
print "Hello, world!\n";

このプログラムを HHVM で実行します。正しく実行されました。

$ hhvm hello.hh
Hello, world!

次に、PHP と Hack の実行速度を比較してみます。Hack の最大の特徴は PHP に型アノテーションを持たせたところだと思いますので、その効果を確認するための例題として、以前に下記の記事で試したプログラムを利用することにします。
データを正しい型で扱うことによる高速化 - y_uti のブログ

PHP のプログラムは以下のとおりです (prog1.php 再掲)。

<?php
$arr = array();
while ($line = fgets(STDIN)) {
    $arr[] = trim($line);
}

$len = count($arr);
$result = 0;
for ($i = 0; $i < $len; $i++) {
    for ($j = 0; $j < $len; $j++) {
        $result -= $arr[$i] + $arr[$j];
    }
}
print $result . "\n";

ランダムな 5000 行のファイルを作って PHP で実行した結果が以下になります。

$ for i in `seq 5000`; do echo $RANDOM; done >a.txt
$ time php prog1.php <a.txt
-817061200000

real    0m5.740s
user    0m5.727s
sys     0m0.005s

まずは、これをこのまま Hack プログラムに書き換えて実行してみます。なんと PHP の 2 倍以上の時間がかかってしまいました。

$ time hhvm prog1.hh <a.txt
-817061200000

real    0m11.774s
user    0m11.706s
sys     0m0.052s

これは、prog1.hh の変数がすべてグローバル変数であることが理由だと考えられます。HHVM は型情報を用いた最適化によってプログラムを高速に実行するということですが、グローバル変数の型は決められないので、最適化の効果が得られないのでしょう。

このことを確かめるため、prog1.hh の全体を関数に入れた形にプログラムを書き直して、実行してみます (prog1b.hh)。

<?hh
function main() {
  $arr = array();
  while ($line = fgets(STDIN)) {
      $arr[] = trim($line);
  }

  $len = count($arr);
  $result = 0;
  for ($i = 0; $i < $len; $i++) {
      for ($j = 0; $j < $len; $j++) {
          $result -= $arr[$i] + $arr[$j];
      }
  }
  print $result . "\n";
}

main();

実行結果は以下のとおりです。予想どおり、prog1.hh と比べて実行時間が大幅に短縮されました。

$ time hhvm prog1b.hh <a.txt
-817061200000

real    0m4.244s
user    0m4.107s
sys     0m0.130s

念のため、PHP ではこのような差が出ないということも確認しておきます。実行例の prog1b.php は prog1b.hh の先頭行を <?php に変えたものです。

$ time php prog1b.php <a.txt
-817061200000

real    0m5.484s
user    0m5.466s
sys     0m0.008s

以前の記事では、読み込んだデータを intval で整数にしてから $arr に格納することで、プログラム後半の加算を高速化できることを示しました。prog1b.php を以下のように書き換えて、このことを改めて確認します。

$ diff prog1b.php prog2.php
5c5
<       $arr[] = trim($line);
---
>       $arr[] = intval(trim($line));

$arr の各要素には intval で変換済みの整数値が格納されているので、加算のたびに値を文字列から整数に変換する処理が不要となり、高速に実行できるようになります。実行時間を prog1b.php の半分くらいに短縮できています。

$ time php prog2.php <a.txt
-817061200000

real    0m2.686s
user    0m2.682s
sys     0m0.000s

prog2.php と同じ内容を Hack で書いて実行した結果は以下のとおりです。PHP よりも劇的な効果があるようで、実行時間は prog1b.hh の 15% 程度になりました。まだ HHVM のソースコードを追っていないので推測でしかないのですが、Hack では、型推論によって $arr が array<int> であることが静的にわかっているので*1、実行時の型チェックが不要になり*2、このように高速に実行できるのだと思います。

$ time hhvm prog2.hh <a.txt
-817061200000

real    0m0.664s
user    0m0.378s
sys     0m0.278s

*1:おそらく、intval の戻り値は int であるという情報を持っていて、そのことから、右辺が int なので左辺も int だとわかり、したがって $arr が array<int> だと推論できるのでしょう。

*2:PHP の場合には、あくまでも、加算の対象となるデータの型を調べた結果として、それが整数値だということがわかり、整数同士の加算が実行されるという流れになります。したがって、加算のたびにデータの型を調べるという処理そのものを省くことはできません。