2016-11-10

PHPのextensionを作ってみた

PHPのextensionをCで直接書く方法(公式)とZephirで書く方法の2パターンで作ってみました。

Cで作る

まずはPHPのソースをダウンロード
$ wget http://jp2.php.net/get/php-5.6.26.tar.gz/from/this/mirror
$ tar xzf php.tar.gz

extensionのスケルトンフレームを作成してビルド

$ cd php-5.6.26/ext
$ ./ext_skel --extname={EXTENSION NAME}
$ cd {EXTENSION NAME}
$ vim config.m4
$ ./configure --enable-{EXTENSION NAME}
$ make

config.m4は以下のコメントアウト(dnl)を削除すればOK

PHP_ARG_ENABLE(hoge, whether to enable {EXTENSION NAME} support,
Make sure that the comment is aligned:
[  --enable-{EXTENSION NAME}           Enable hoge support])

ビルド後は以下のようにして実行可能(もちろんphp.iniにextensionのパスを入れてモジュールを読み込んでもOK)

$ php -d extension=modules/{EXTENSION NAME}.so -r "{input your code}"

Zephirで作る

Zephirのインストール
$ git clone https://github.com/phalcon/zephir
$ cd zephir
$ ./install -c

プロジェクトを作成

$ zephir init ZephirHello # zephir init {PROJECT_NAME}

クラスの作成(zephirhello/hello.zep)

namespace ZephirHello;

class Hello
{
    public static function hoge()
    {
        echo "hello world!";
    }
}

以下のコマンドでZephirからCのコードを作成(Generate)、Cのコードをビルド(Compile)、モジュールのインストール(Install)を行います。

$ zephir build

ただし、Mac(El CapitanやSierra)で開発する場合はRootlessのセキュリティ機能によって/usr以下にインストールすることができないため、build時に以下のエラーが発生します。

$ cat compile-errors.log  
cp: /usr/lib/php/extensions/no-debug-non-zts-20131226/#INST@88099#: Operation not permitted
make: *** [install-modules] Error 1

この場合は、インストール(=ファイルの移動)はせずに、php.iniにコンパイルしたモジュールへのフルパスを書けばOKです。

extension=/Users/xxxx/work/php/zephirhello/ext/modules/zephirhello.so

上記のやり方だと、モジュールのインストールは不要になるので、以下のコマンドでビルドまで行えばOKです。

$ zephir compile

また、re2cがインストールされていないと、コンパイル時に以下のエラーが発生します。

zephir_parser extension not loaded, compiling it
error: re2c is not installed
make: *** No rule to make target `clean'.  Stop.
Preparing for parser compilation...
configure: WARNING: You will need re2c 0.13.4 or later if you want to regenerate PHP parsers.
Compiling the parser...

OS Xの場合はHomeBrewでre2cをインストールすればOKです。

$ brew install re2c

名前空間とディレクトリ、クラス名の構成があっていない場合もエラーが発生します。

Zephir\CompilerException: Unexpected class name Utils\HelloWorld in file: 
zephirhello/hello.zep, expected: utils/helloworld.zep

上記のエラーは名前空間Utils、クラス名HelloWorldが指定されているので、ファイル構成がutils/helloworld.zepにならないといけないのに、zephirhello/hello.zepとなっているのが原因になります。この場合は、ファイル構成を変更するか、名前空間、クラス名を変更すれば良いです。

モジュールのビルド及びphp.iniでのモジュール設定が完了したら、以下のようにしてクラス、メソッドを呼び出し可能です。

$ php -r "echo ZephirHello\\Hello::hoge();"

phpinfoでモジュールが読み込まれていることも確認できます。

$ php -i 
....

zephirhello



zephirhello => enabled
Author =>  
Version => 0.0.1
Build Date => Nov  9 2016 12:10:50
Powered by Zephir => Version 0.9.4a-dev-e58a876fd4

....

ベンチマーク(準備)

再帰呼び出しのフィボナッチ数列のロジックでベンチマークを以下のパターンで取ってみました。 PHP
<?php

function fib($n) {
  if ($n < 2) {
    return $n;
  }
  return fib($n-1) + fib($n-2);
}
echo fib($argv[1]);

PHP(C)

long fib (long n) {
    if (n < 2) {
        return n;
    }
    return fib(n-1) + fib(n-2);
}

PHP_FUNCTION(hoge)
{
    long arg;
    int arg_len, len;
    char *strg;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &arg, &arg_len) == FAILURE) {
        return;
    }

    RETURN_LONG(fib(arg));
}

const zend_function_entry hoge_functions[] = {
    PHP_FE(hoge, NULL)
    PHP_FE_END
};

PHP(Zephir)

namespace ZephirHello;

class Hello
{
    public static function fib(int n) -> int
    {
        if n < 2 {
            return n;
        }
        return self::fib(n - 1) + self::fib(n - 2);
    }
}

Ruby

def fib(n)
  if n < 2
    return n
  end
  return fib(n-1) + fib(n-2)
end

puts fib(ARGV[0].to_i)

Python

import sys


def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

print(fib(int(sys.argv[1])))

ベンチマーク(結果)

まとめるとこんな感じ。
パターン Index  時間 
PHP(5.6.23) 33 9.93s
PHP(5.6.23、C) 40 0.66s
PHP(5.6.23、Zephir) 33 11.95s
Ruby(2.2.4) 40 14.26s
Python(2.7.11) 40 42.46s
 

PHPはIndex: 33の計算でも10秒近くかかります。40だと1000秒近くかかりそうだったのでPHPのベンチマークはここで断念。

$ time php bench.php 33
3524578
php bench.php 33  9.92s user 0.05s system 99% cpu 10.049 total

extensionのCで書いたものはIndex: 40で1秒かかりません。やはり早いです。

$ time php -d extension=modules/hoge.so -r "echo hoge(40);"
102334155
php -d extension=modules/hoge.so -r "echo hoge(40);"  0.66s user 0.01s system 97% cpu 0.687 total

Zephirで作ったものは素のPHP同様、Index: 33で10秒かかっていたのでここで断念。PHP版とそんなに変わらないみたいですね…。

$ time php -r "echo ZephirHello\\Hello::fib(33);"
3524578
php -r "echo ZephirHello\\Hello::fib(33);"  11.95s user 0.10s system 95% cpu 12.616 total

Rubyで書いたものはIndex: 40で14.5秒程度。

$ time ruby bench.rb 40
102334155
ruby bench.rb 40  14.26s user 0.08s system 99% cpu 14.474 total

PythonはIndex: 40で45秒程度。3系も似たような結果でした。

$ time python fib.py 40
102334155
python fib.py 40  42.46s user 0.36s system 95% cpu 44.665 total

参考URL

このエントリーをはてなブックマークに追加