16
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHPエクステンションが作れるZephirのコンパイラとコードの最適化の話

Last updated at Posted at 2014-09-18

いつの間にやらZephirのバージョンが0.5に上がっていました。
Zephirで開発されているphalconのほうもこの間とうとうベータ2になったようです。
PHP5.6のリリースやJITコンパイラの実装計画、hackの登場など今年のPHP界隈は色々と話題を欠かさないですね。
(最近のPHP本体のアップデートにCVE系のアップデートが多いのが気になりますが。。。)

そんな盛り上がりを見せる中、PHPカンファレンス2014の開催ももうすぐということで、
登壇する代わりにQiitaにZephirの話を載せてPHPの盛り上がりに火をくべることにしましょう。

What's Zephir ?

Zephirが何か?についてはQiitaの以下の投稿を見ればどんなものなのかは理解出来るでしょう。

ZephirはどのようにしてPHPエクステンションを作るのか

先に挙げたリンクを読めばZephir使えば未知の世界だったエクステンションつくるのとか楽勝!!
と思うようになっていただけると思います。
でも、作れるだけではなくてどんなふうに作られているのかまで知っているのがエンジニアってやつですよね。

ZephirのコンパイラはPHPで出来ています

もしかすると、PHPがテンプレートエンジンだと信じて疑わない人には何を言っているのか理解できないかもしれません。
あるいは、何でも適材適所と言い切ってPHPの可能性を信じていない人(実際に適材適所はあると思いますが;)にも全く理解できないかもしれません。

ZephirのコンパイラはPHPで出来ています


ちょっと大袈裟に書きましたが、PHPがコンパイルして出力するのはCのコードであってバイナリではありません。
coffescriptのコンパイラやstylusのコンパイラをPHPで実装したライブラリが幾つか存在していることを考えればどうということはないでしょう。
(TypeScriptというAltJsな言語だってコンパイラはjavascriptで書かれていますしね。)

ZephirはPHPのコンパイラによってCのコードにコンパイルされ、
そこからさらにgccによってコンパイルされてエクステンションに仕上がっているだけなのです。

イメージ図(イメージ図の編集リクエスト募集!)
compile-image.png

PHPでコンパイルされるのでコンパイル速度に期待してはいけません。

コードを見てみる

サンプルコードからZephirがどのようなCのコードを生成するのか見てみましょう。

test/test/greet.zep
namespace Test;

class Test
{
    public static function inArray(var needle, array arr)
    {
        return in_array(needle, arr);
    }
}

このコードをコンパイルすると、extフォルダの中にnamespaceと同じ名前のフォルダが生成されているのが確認出来ます(今回はtest)。

greet.zep.cgreet.zep.hというC言語だと思わずにはいられないファイルができていることが確認出来ます(実際に中はC言語です)。

ext/
  .
  .
  .
  test/
    .libs/
      test.lo
      test.zep.c
      test.zep.h

コンパイルされたCのコードはこんな感じになります。

ext/test/test.zep.c
#ifdef HAVE_CONFIG_H
#include "../ext_config.h"
#endif

#include <php.h>
#include "../php_ext.h"
#include "../ext.h"

#include <Zend/zend_operators.h>
#include <Zend/zend_exceptions.h>
#include <Zend/zend_interfaces.h>

#include "kernel/main.h"
#include "kernel/array.h"
#include "kernel/operators.h"
#include "kernel/memory.h"


ZEPHIR_INIT_CLASS(Test_Test) {

	ZEPHIR_REGISTER_CLASS(Test, Test, test, test, test_test_method_entry, 0);

	return SUCCESS;

}

PHP_METHOD(Test_Test, inArray) {

	zval *arr = NULL;
	zval *needle, *arr_param = NULL;

	ZEPHIR_MM_GROW();
	zephir_fetch_params(1, 2, 0, &needle, &arr_param);

	zephir_get_arrval(arr, arr_param);


	RETURN_MM_BOOL(zephir_fast_in_array(needle, arr TSRMLS_CC));

}

Zephirはエクステンションを作るにあたってコードを最適化してくれる

さて、感の良い人やコード読むのが大好きな人は先に載せたコンパイル後のコードを見ておや?と感じたはずです。

元のZephirのコードは引数を受け取って中でin_arrayを呼び出しているだけなので、
コンパイル後のコードはPHP本体のin_array実装で利用されているphp_search_array関数が呼び出されていればいいはずです。
しかし、コンパイルされたZephirでは最終的にzephir_fast_in_arrayという関数が呼ばれていることが分かります。

Zephirからphpの標準関数と同じ名前の関数を呼ぶ処理を書いた場合、コンパイル時にZephirが提供する最適化されたphpの標準関数と同等の機能を持つ関数が呼ばれるようになるのです。
この仕組によってZephirはプログラムのパフォーマンスを向上させることを実現しているのです。
(最も、これは速度向上のための一側面でしかありませんが…)

速度を比較してみよう

先に載せたinArrayとphpのin_arrayを使って本当にパフォーマンスが向上しているのか確認してみましょう。
phpのin_arrayといえば計算量がO(n)になることで悪評が高いですね。

1~1000までの配列から値を探す処理を書いて3つのパターンで処理速度を比較してみます。

普通のin_array
function measure($search)
{
    $start = microtime(true);
    $array = range(1, 1000);

    foreach (range(1,10000) as $i) {
        in_array($search, $array);
    }

    var_dump(microtime(true) - $start);
}

measure(1);    // 1~1000の配列から1を探す
measure(500);  // 1~1000の配列から500を探す
measure(0);    // 1~1000の配列から0を探す(見つからない)
Zephirのin_array
function measure($search)
{
    $start = microtime(true);
    $array = range(1, 1000);

    foreach (range(1,10000) as $i) {
        \Test\Test::inArray(1, $array);
    }

    var_dump(microtime(true) - $start);
}

measure(1);    // 1~1000の配列から1を探す
measure(500);  // 1~1000の配列から500を探す
measure(0);    // 1~1000の配列から0を探す(見つからない)

以下のような結果になりました。

普通のin_array Zephirのin_array
1~1000の配列から1を探す 0.0066111087799072 0.0051839351654053
1~1000の配列から500を探す 0.077008008956909 0.051916122436523
1~1000の配列から0を探す(見つからない) 0.14873790740967 0.096560001373291

全てのケースにおいてZephirのin_arrayのほうが高速に動作することが確認出来ます。
また、1~1000の配列から500を探す、__1~1000の配列から0を探す(見つからない)__ケースのように配列の後ろの方の値を探す場合より高速に動作していることが確認出来ます。

補足とか

  • zephir0.5が出たのにバージョン0.4.6で書いてるのは0.5だとスケルトンを生成した時にコンパイルに必要な情報を書くconfig.jsonが作成されなくなっていたためです。(どう書いていいのか調べるの面倒だった)
  • やらたコンパイラという単語が飛び交いますが、本文中でのコンパイラは何かを変換して別の何かを出力するものとして読み進めて下さい。
  • 冒頭でphpカンファレンス2014に登壇する変わりなどと宣っていますが、私自身にはそれほどの実績も知識もございません、申し訳ありません。
  • Zephirがパフォーマンスを向上させるのはコードの最適化だけでなく、エクステンションにコードをまとめることでrequireの数を削減出来ることにもあります。

おまけ

Zephirの公式サイトにはSublimeTextTextMateにSyntaxハイライト提供されていると書いてあります。
実はintelliJ(多分phpstormもあるはず)にもzephirのプラグインがあります(今のところSyntaxハイライトされるぐらい)。

16
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?