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

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

そんな盛り上がりを見せる中、[PHPカンファレンス2014](http://phpcon.php.gr.jp/w/2014/)の開催ももうすぐということで、
登壇する代わりにQiitaにZephirの話を載せてPHPの盛り上がりに火をくべることにしましょう。

# What's Zephir ?

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

* [メモリ操作を気にせずにPHPエクステンションを作れる言語”Zephir”](http://qiita.com/k-motoyan/items/75222492fd1eafe0c54a)
* [Zephir使えばPHP Extensionが誰でも超絶簡単に作れる](http://qiita.com/taruhachi/items/8fb38b08cba4d83871b9)

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

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

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

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

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

---

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

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

イメージ図（イメージ図の編集リクエスト募集！）
![compile-image.png](https://qiita-image-store.s3.amazonaws.com/0/28406/94e5dd3a-a4dc-7d9b-b644-2831e097d1c2.png)

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

### コードを見てみる

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

```php: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.c`と`greet.zep.h`というC言語だと思わずにはいられないファイルができていることが確認出来ます（実際に中はC言語です）。

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

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

```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までの配列から値を探す処理を書いて３つのパターンで処理速度を比較してみます。

```php:普通の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を探す(見つからない)
```

```php: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の公式サイトには`SublimeText`と`TextMate`にSyntaxハイライト提供されていると書いてあります。
実はintelliJ（多分phpstormもあるはず）にもzephirのプラグインがあります（今のところSyntaxハイライトされるぐらい）。