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
- Cで書いたExtensionを利用したPHP
- Zephirで書いたExtensionを利用したPHP
- Ruby(参考)
- Python(参考)
<?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