Zephirで62進数10進数変換PHPエクステンションを作る


最初に

Phalcon 2.0でも採用されているZephirを使うと、PHPエクステンションが(比較的)簡単にできる。しかもコンパイル済みなのでとっても速い。ということで、試しに62進数10進数を作ってみた。
環境はCentOS7のインストール直後です。

Zephirインストールの前準備

bash
# yum install gcc automake autoconf libtool php-devel
# yum install http://pkgs.repoforge.org/re2c/re2c-0.13.5-1.el6.rf.x86_64.rpm

※ re2cは最新版だと他パッケージとの依存関係で面倒なので、上記バージョンを入れる。他のリポジトリを使っていて、そっちにre2cがあればyumで入れてもOK。

インストール

bash
# cd /usr/local/src
# git clone https://github.com/phalcon/zephir
# git clone https://github.com/phalcon/json-c zephir/json-c
# cd zephir
# ./install-json
# ./install -c
# zephir version
0.5.9a
# php -v
PHP 5.4.16 (cli) (built: Oct 31 2014 12:59:36)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies

コードを書く

bash
# cd
# zephir init orenokaisha
# cd orenokaisha
# vi orenokaisha/utils.zep

zephirのソースはこちら。拡張子がzepですね。

orenokaisha/utils.zep
namespace Orenokaisha;

class Utils
{
    const HASH_TABLE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    public static function b10to62(long value) -> string
    {
        string result = "0";
        if (value > 0) {
            long i; string hashtable, ch = "";
            let hashtable = self::HASH_TABLE;
            let result = "";
            while(value > 0) {
                let i = value % 62;
                let ch = hashtable[i];
                let result =  ch . result;
                let value = (value - i) / 62;
            }
        }
        return result;
    }

    public static function b62to10(string value) -> long
    {
        string hashtable; long result = 0, len; int i;
        let hashtable = self::HASH_TABLE;
        let len = value->length() - 1;
        for i in reverse range(0, len) {
            let result += pow(62, len - i) * hashtable->index(chr(value[i]));
        }
        return result;
    }

}

※ ちなみに0以下の数値が入ってきた場合は、全て0で返してますので、ご注意を。

buildとエクステンションの登録と確認

bash
# zephir build
# echo 'extension=orenokaisha.so' > /etc/php.d/orenokaisha.ini
# php -i|grep 'orenokaisha'

※ php.iniのtimezoneは設定しておくべし

比較実験のコード

ではどのくらい速いのか比較して実験してみましょう♪ 100万回ループで、最後にどんだけ速いかの倍率がでます。

test.php
<?php
class Utils
{
    const HASH_TABLE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    public static function b10to62($value)
    {
        $result = "0";
        if ($value > 0) {
            $hashtable = self::HASH_TABLE;
            $result = "";
            while($value > 0) {
                $i = $value % 62;
                $ch = $hashtable[$i];
                $result =  $ch . $result;
                $value = ($value - $i) / 62;
            }
        }
        return $result;
    }

    public static function b62to10($s)
    {
        $result = 0;
        $hashtable = self::HASH_TABLE;
        $len = strlen($s);
        for ($i = 0; $i < strlen($s); $i++) {
            $result += pow(62, $len - 1 - $i) * strpos($hashtable, ($s[$i]));
        }
        return $result;
    }

}

function test1($value)
{
    $b62 = Orenokaisha\Utils::b10to62($value);
    $b10 = Orenokaisha\Utils::b62to10($b62);
    return ($b10 === $value);
}

function test2($value)
{
    $b62 = Utils::b10to62($value);
    $b10 = Utils::b62to10($b62);
    return ($b10 === $value);
}

$count = 10000 * 100;

$ok1 = 0;
$err1 = 0;
$time1 = microtime(true);
for ($i = 0; $i < $count; $i++) {
    if (test1($i)) {
        $ok1++;
    } else {
        $err1++;
    }
}
$lap1 = microtime(true) - $time1;

$ok2 = 0;
$err2 = 0;
$time2 = microtime(true);
for ($i = 0; $i < $count; $i++) {
    if (test2($i)) {
        $ok2++;
    } else {
        $err2++;
    }
}
$lap2 = microtime(true) - $time2;

echo "test1: $ok1, $err1 => $lap1\n";
echo "test2: $ok2, $err2 => $lap2\n";
echo $lap2 / $lap1 . "\n";

結果

php
# php test.php
test1: 1000000, 0 => 2.6385660171509
test2: 1000000, 0 => 3.9418640136719
1.4939417805162

結果は、どどーんと1.49倍!!(処理時間は環境によるので、相対値のみ気にしてください)
って、あれあれあれー、これだけ?
10倍とか100倍とか速いのを期待していたわけですが、そもそもあまり複雑な処理ではなくて元から結構速いということもあり、そんなもんみたいです。
苦労の割にちょっぴり残念な結果ですが、requireの数が減るという特典もありますしね、、、。