PHP拡張機能の作成

そして今日は、少し異なる視点からPHPを見て、拡張機能を作成しましょう。 このトピックに関するHabréの出版物( ここここ )がすでにあるので、それがなぜ役に立つのか、実際に何が使えるのかについては掘り下げません。 この記事では、Visual C ++を使用するWindowsおよびGCCを使用するDebianで簡単な拡張機能を構築する方法を説明します。 また、拡張機能内のPHP配列の動作を強調し、ネイティブPHPで記述されたアルゴリズムとCで記述されたコードを使用したアルゴリズムのパフォーマンスを比較します。


Win32でのコンパイル


それでは、Windowsから始めましょう。 ご存知のように 、PHP開発者はVisual C ++ 9またはVisual Studio 2008を使用して、Windowsで頭脳をコンパイルします。 したがって、Visual Studio 2008を使用します。ただし、無料のExpressバージョンも適していますが、おそらく、スタジオの新しいバージョンと古いバージョンが適しています。

必要なもの:まず、Win32コンソールアプリケーションタイプのプロジェクトを作成し、アプリケーションタイプでDLLを選択します。 ここで、リンカーのすべての依存関係とパスを構成する必要があります。プロジェクトでstdafx.hファイルを見つけて、その内容を次のものに置き換えます。
#ifndef STDAFX #define STDAFX #define PHP_COMPILER_ID "VC9" //        PHP,  Visual C++ 9.0 #include "zend_config.w32.h" #include "php.h" #endif 

この段階でプロジェクトをコンパイルしようとすると、main \ config.w32.hが見つからないというエラーが表示されます。 main \ configure.batスクリプトを実行するか、ソース(PHPバージョン5.2など)からプルすることで取得できます。 この場合、このファイルのすべてのパスを編集し、「#define HAVE_SOCKLEN_T」ディレクティブのコメントを外すことを忘れないでください。 これで、プロジェクトはエラーなしでコンパイルされます。

hello worldを作成して、cppファイルに次を追加します。

 PHP_FUNCTION(test); const zend_function_entry test_functions[] = { PHP_FE(test, NULL) {NULL, NULL, NULL} }; zend_module_entry test_module_entry = { STANDARD_MODULE_HEADER, // #if ZEND_MODULE_API_NO >= 20010901 "test", //   test_functions, //    NULL, // PHP_MINIT(test), Module Initialization NULL, // PHP_MSHUTDOWN(test), Module Shutdown NULL, // PHP_RINIT(test), Request Initialization NULL, // PHP_RSHUTDOWN(test), Request Shutdown NULL, // PHP_MINFO(test), Module Info ( phpinfo()) "0.1", //    STANDARD_MODULE_PROPERTIES }; ZEND_GET_MODULE(test) PHP_FUNCTION(test) { RETURN_STRING("hello habr", 1); //  PHP-,   ,         } 

このモジュールをPHPに接続して、次のようなものを実行してみてください。
 php -r "test();" 
答え「hello habr」を取得する必要があります。


* nixでのコンパイル


* nixでは、すべてがいつものようにシンプルになりました。 Debianの例を紹介しますが、他のシステムでもプロセスは変わらないと思います。
以下が必要です。拡張機能用のディレクトリを作成しましょう。 さて、例えば/テスト。 そこで、2つの空のファイルを作成します。
 config.m4
 test.c

1つ目は拡張機能を魔法のようにコンパイルするために必要で、2つ目はそのソースコードです。 config.m4で、次を記述します。
 PHP_ARG_ENABLE(test, Enable test support) if test "$PHP_TEST" = "yes"; then AC_DEFINE(HAVE_TEST, 1, [You have test extension]) PHP_NEW_EXTENSION(test, test.c, $ext_shared) fi 

test.cの追加
 #include "php.h" 

そして、この期限の後、cppファイルの内容をWindowsバージョンからコピーします。
次に、コンソールに移動して:
 # phpize //        # ./configure //  makefile # make //  # make install //  .so    PHP  

以上です。 これでphp.iniを開き、そこに拡張機能を追加できます:
拡張子= test.so

そして、そのパフォーマンスチームをチェック
 php -r "test();" 


引数の処理と戻り値



まず、引数を取る方法を見てみましょう。
 char* text; int text_length; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &text, &text_lenght) == FAILURE) { return; } 

3番目のパラメーターは、予想されるタイプを示します( ここではすべてのオプションを見ることができます)。この場合、char *またはintです。 このリンクには、型を組み合わせて引数の数を指定するためのオプションもあります。 以下のパラメーターはすべて、転送された値が書き込まれる変数です。 文字列を転送すると、文字列自体とその長さが送信されます。
関数に渡された引数の数が一致しない場合、E_WARNINGがスローされますが、エラーメッセージなどの値を返すことができます。

単純型と複合型の両方を返すことができます。 返された配列の形成に慣れてみましょう。 配列が返されることを示すには、初期化する必要があります。
 array_init(result); 

配列に値を追加するには、どのインデックスと値が配列に追加されるかに依存する関数を使用する必要があります。 例:
 add_next_index_long(result, 42); // $result[] = 42; add_assoc_bool(result, "foo", 1); // $result['foo'] = true; add_next_index_null(result); // $result[] = NULL; 

機能の完全なリストはここにあります。

誰かが興味を持っているなら、次の記事でオブジェクトを操作する例を検討できます(オブジェクトの拡張機能の典型的な例はmysqliです)。 このトピックに関する非常に良い記事があります。


性能


パフォーマンスをテストするために、文字列内の各文字の出現をカウントする、やや合成的な例を選択しました。 つまり、パラメーターとして文字列を受け取り、特定の文字列内の各文字の使用回数を示す配列を返す関数を取得する必要があります。 この例では、大きな文字列を操作する方法を示します。

私はそのような実装を手に入れましたが、コードをあまり蹴らないでください、私はまだCよりもPHPで書いています:

 PHP_FUNCTION(calculate_chars) { char* text; int text_length; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &text, &text_length) == FAILURE) { return; } array_init(return_array); int table[256] = { 0 }; for (int i = 0; i < text_length; i++) { table[((unsigned char*)text)[i]]++; } char str[2]; str[1] = '\0'; for (int i = 0; i < 256; i++) { if (table[i]) { str[0] = (char)i; add_assoc_long(return_array, str, table[i]); } } } 

このコードは次の結果を生成します。
 user> php -r "print_r( calculate_chars('example') );" Array ( [a] => 1 [e] => 2 [l] => 1 [m] => 1 [p] => 1 [x] => 1 } 

ここで、このコードの実行速度とネイティブPHPの同様の実行速度を比較してみましょう。

 $map = array(); for ($i = 0; $i < $length; $i++) { $char = $text[$i]; if (isset($map[$char])) { $map[$char]++; } else { $map[$char] = 1; } } 

マイクロタイム機能を使用して、両方のソリューションの実行時間を比較します。 100文字の文字列、5000文字の文字列、69,000文字の文字列(Charles Dickensの著書A Message from the Seaを取りました。彼が私を許してくれることを願っています)を選択し、各オプションに対して両方のソリューションを数千回実行します。 結果を下の表に示します。 テストはあまり強力ではない自宅のラップトップとDebianを搭載したVDSで実行されました。はい、結果は構成、オペレーティングシステムのバージョン、PHP、気圧、風向に依存する可能性があることを明確に理解していますが、おおよその数値のみを表示したかったのです。
テストスクリプトの完全なコードは、 ここからダウンロードできます 。 拡張機能自体のソースとバイナリは、 こちら(win)こちら(nix)からダウンロードできます。
反復回数PHPコード/ Win32PHPコード/ DebianPHP拡張/ Win32PHP拡張/ DebianWin32 winDebianの勝利
1. 100文字の文字列1,000,00084.7566秒72.5617秒8.4750秒4.4175秒10回16.43回
2.行5000文字10,00039.1012秒31.7541秒0.5001秒0.134秒78.19回236.98回
3. 69,000文字の文字列100052.3378秒44.0647秒0.4875秒0.0763秒107.36回577.51回

結論


解釈されたコードと比較したモジュールのパフォーマンスから判断すると、具体的な結果は、大量のデータと少量の反復で得られることがわかります。 つまり、頻繁に使用されるリソース集約的なアルゴリズムではありませんが、それらをコンパイルされたコードに入れるのは意味がありません。 しかし、大量のデータを処理するアルゴリズムの場合、これは実用的です。 また、私の測定に基づいて、PHPコードの結果は異なるシステムで比較できることがわかります(2つの異なるマシンだったことを思い出します)が、拡張の結果は非常に異なっています。 このことから、私には知られていないコンパイル機能がいくつかあると個人的に結論付けました。 ただし、PHPプロジェクトにWindowsサーバーを使用している人はいないと強く思います。 また、誰かが今すぐCで何かを書き直そうとするのではないかと疑っていますが、この記事はまだ行動のガイドというよりも楽しいものです。 PHP拡張モジュールの記述は非常に簡単で、時には非常に役立つ場合があることを示したかっただけです。

UPD1。 count_charsとの比較
コメントで興味深い質問がされました:count_chars関数のパフォーマンスと比較したらどうでしょうか?
繰り返し回数を100回増やし、同じテストを実行しましたが、既にこの関数を使用しています。 Debianでの結果はほぼ同等であり、Windowsでは興味深い状況があります。データが多いほど、パフォーマンスが向上するのは私のモジュールです。 テストのアイデアは自転車を書くことではなく、大量のデータを処理するアルゴリズムを採用することでした。
反復回数count_chars / win32count_chars / Debian拡張機能/ win32拡張機能/ DebianWin32 winDebianの勝利
1. 100文字の文字列10,000,00067.5245秒47.8104秒81.8185秒43.8091秒0.83回1.09回
2.行5000文字1,000,00022.4693秒12.8959秒47.2514秒12.9577秒0.48回0.99回
3. 69,000文字の文字列100,00015.0681秒7.661秒46.9598秒7.7387秒0.32回0.99回


素材

Source: https://habr.com/ru/post/J125597/


All Articles