Zephir使って、既存のコードと比較してみた

PhalconがZephirで書き換えられたようで、今後のバージョンアップの速度も上がってくれるといいですね。
という事で、Zephirそんなにいいものなのかい？と思ったので触ってみました。

まず、インストール方法に関しては公式や他の方に譲りますので、適当に入れてください。
今回は[ガチャのサンプルコード](http://qiita.com/chiyoyo/items/94b4c04447abb87d948a#%E9%85%8D%E5%88%97%E3%81%8B%E3%82%89%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%81%AB%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)に記載している、配列からランダムに取得するメソッドを作って実行速度を比較してみました。

# extentionを作る

## プロジェクト作成

プロジェクトを作成して、できたディレクトリへ移動

```bash
$ zephir init gacha
$ cd gacha
```

## Zephir版クラス作成
ソースを作成。既存のソースをコピペしてから作成していきます。

今回修正した点は以下となります。
* namespaceをつける
* 変数初期化は必ずする
* 変数名から$をとる
* シングルクォートをダブルクォートに変更
* foreachをforに変更
* インクリメント(++)・デクリメント(--)や+=、-=などを除去
* 組み込み済みクラス(Exception等)のクラス名の先頭に\を入れる

今回はなかったが、他に気をつけて見た所
* else ifを除去
* 既存for文はfor i in range(start, end)に修正

```zephir:gacha/Utility.zep
// namespaceの先頭の文字は大文字にしないとダメみたい
namespace Gacha;

class Utility {
    /**
     * 配列の中から確率を使いランダムで1件取得する
     * @param array データ配列
     * @param string 確率を表す連想配列名(デフォルトは"rate")
     */
    public static function getRandomData(array data, string keyName = "rate")
    {
        // 確率の合計値を求める
        int max = 0;
        var record;
        for record in data {
            let max = max + record[keyName];
        }

        // 合計値までのランダム値を求める
        var rate;
        let rate = mt_rand(0, max - 1);

        // 合計値までのランダム値を求める
        var rate;
        let rate = mt_rand(0, max - 1);

        // ランダムの値がどれにあたるか調べる
        for record in data {
            let max = max - record[keyName];
            if (max <= rate) {
                return record;
            }
        }
        throw new \Exception("You should not come here...");
    }
}
```

## コンパイル
コンパイル中にsudoが入るようなので、権限を持っているアカウント推奨
(.soファイルを移動する際に必要なのかと思います)

```bash
$ zephir build
```

## php.iniに組み込み & 再起動

```bash
$ sudo echo "extension=gacha.so" > /etc/php.d/gacha.ini
$ sudo /etc/init.d/httpd restart
```

# パフォーマンステスト
はい、これがやりたかったのですよ。
という事で、さっそくソースを

## ガチャ用データ

```php:data.php
<?php
// ガチャ用データ
return $data = array(
    array('card_id'=>1, 'rate'=>'10'),
    array('card_id'=>2, 'rate'=>'10'),
    array('card_id'=>3, 'rate'=>'10'),
    array('card_id'=>4, 'rate'=>'10'),
    array('card_id'=>5, 'rate'=>'10'),
    array('card_id'=>6, 'rate'=>'10'),
    array('card_id'=>7, 'rate'=>'10'),
    array('card_id'=>8, 'rate'=>'10'),
    array('card_id'=>9, 'rate'=>'10'),
    array('card_id'=>10, 'rate'=>'10'),
);
```

## PHP版クラス作成
.soにした元のソースコード

```php:Utility.php
<?php
class Utility {
    /**
     * 配列の中から確率を使いランダムで1件取得する
     * @param array データ配列
     * @param string 確率を表す連想配列名(デフォルトは"rate")
     */
    public static function getRandomData(array $array, $keyName = 'rate') {
        // 確率の合計値を求める
        $max = 0;
        foreach ($array as $record) {
            $max += $record[$keyName];
        }

        // 合計値までのランダム値を求める
        $rate = mt_rand(0, $max-1);

        // ランダムの値がどれにあたるか調べる
        foreach ($array as $record) {
            $max -= $record[$keyName];
            if ($max <= $rate) {
                return $record;
            }
        }
        throw new Exception('ここには来ないはず');
    }
}
```

## テストコード(extention版)

```php
<?php
$number = 100000;

require "data.php";

$result = array();
$start = microtime(true);
for ($i = 0 ; $i < $number ; $i++) {
    $record = Gacha\Utility::getRandomData($data);
    if (isset($result[$record['card_id']])) {
        $result[$record['card_id']]++;
    } else {
        $result[$record['card_id']] = 1;
    }
}
$lap = microtime(true) - $start;

print_r($result);
echo 'LAP: ' . $lap . "\n";

## テストコード(そのまま版)

```php
<?php
$number = 100000;

require "Utility.php";
require "data.php";

$result = array();
$start = microtime(true);
for ($i = 0 ; $i < $number ; $i++) {
    $record = Utility::getRandomData($data);
    if (isset($result[$record['card_id']])) {
        $result[$record['card_id']]++;
    } else {
        $result[$record['card_id']] = 1;
    }
}
$lap = microtime(true) - $start;

print_r($result);
echo 'LAP: ' . $lap . "\n";
```

## 実行結果
10万回ガチャを引くことを、20回施行の平均で以下のようになりました。

|種別|実行時間|
|:--:|:--:|
|extention版|0.336985111秒|
|そのまま版|0.541183376秒|

んー、目に見えて早くなってますねー！
既存のソースでよく使うものや、ライブラリ系をextention化するだけで、とっても早くなりそうです。
else ifがなかったり、型の判定なども微妙だったりと、まだまだな言語ですが、自前のライブラリをextention化しようという思想はかなり面白いと思いますので、今後に期待ですね。