PHP Conference Japan 2022 の 09/25に行なわれる dotnet_ffiについての講演資料です。 https://fortee.jp/phpcon-2022/proposal/6fa15e19-4a11-4af0-b5ff-ee19aa8ff931
dotnet_ffiソース https://github.com/pg-ito/dotnet_ffi
文: よーがす Twitter: @pg_ito Blog: https://b64.pw/blog/
PHPからC#のライブラリを呼べるようにしたdotnet_ffiを趣味でつくってみたよーがす @pg_ito(アバター: 佐波野いりこ @SabanoIriko)PHP Conference Japan 2022 09/25
View Slide
自己紹介● エンジニア最近は主にPHP、C#、たまにC++、JavaScriptサーバの負荷対策が好き● 趣味配信、電子工作、スプラトゥーン3名前: よーがすTwitter: @pg_itoBlog: https://b64.pw/blog/
もくじ● dotnet_ffiってなに?● 作った動機● 想定される利用ケース● dotnet_ffiの構成● dotnet_ffiの簡単な使い方● CoreCLRって?● Extensionを作るための情報源● まとめ● 今後の展望
dotnet_ffiってなに?
dotnet_ffiってなに?PHPからC#のライブラリを直接呼べるようにしたPHP ExtensionCoreCLRをつかってC#のDLLを動的にロード、実行しているPHPプロセスが作り直されるまでC#のVMを使いまわすのでC#のJITの恩恵を得られて高速に動作リポジトリ: https://github.com/pg-ito/dotnet_ffi(MITライセンス)
パフォーマンス比較fibonacci数を計算させた場合PHPのみに比べて約16倍ほど高速(PHP8.1.6の場合)
作った動機
作った動機・Unityと同じ処理をPHPサーバ側でも行いたい・Extensionを書かずに手軽にPHPの処理を高速化したい・C#にしかない機能を使いたい
想定される利用ケース
UnityClientと処理の共通化・ゲームのチート対策クライアントから送られてきたデータが不正なものでないか正規のクライアント側のC#の処理と比較できる・バリデータの共通化APIのエラーチェックなどを一元化できる
処理の高速化・CPU使用率の高い処理をC#側で高速に行える例えばfibonacci数を求めるような処理で効果を発揮(リポジトリ内のベンチマークにこのケースを想定したコードを同梱)
C#にしかないライブラリの利用・Linqでのデータの抽出多次元配列を任意の形に整形(おそらくPHPのみで書かれている限りPHP移植版Linqにくらべて高速な…ハズ)
dotnet_ffiの構成
PHP Process設計の特徴・PHP ExtensionがC/C++で書けることを利用してCoreCLR経由でC#とバインドPHP ➡ dotnet_ffi(C,C++) ➡ CoreCLR(C++) ➡ C# LibraryPHP 標準ExtensionCoreCLR (C++)C# LibraryPHP標準関数等PHP言語構造dotnet_ffi (C, C++)※CoreCLRについては後述
PHP8対応での影響・ARG_INFOの対応PHP8でビルドするとついにwarningが出るようになった。if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &arg1, &arg2) == FAILURE) {ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ret_s64_arg_s64_s64,0, 2, IS_LONG, 0)ZEND_ARG_INFO(0, long_arg1)ZEND_ARG_INFO(0, long_arg2)ZEND_END_ARG_INFO()PHP_ME(DotnetFFI, ret_s64_arg_s64_s64, arginfo_ret_s64_arg_s64_s64,ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)型情報を追加
dotnet_ffiの簡単な使い方
ビルド・ソースのクローンgit clone https://github.com/pg-ito/dotnet_ffi.git .・docker composeでビルドと実行cd build_envdocker compose -up -d --builddocker compose exec php_fpm /bin/bash -c "cd /var/www && ./br.sh && ./run.sh"・もし直接ビルドする場合yum install -y devtoolset-7-gcc devtoolset-7-gcc-c++yum install -y --enablerepo=epel,remi,remi-php81 php php-develyum install -y dotnet-sdk-6.0./br.sh
dotnet_ffiの簡単な使い方・引数がint、戻り値もintの場合の呼び出し方$retInt64 = DotnetFFI::ret_s64_arg_s64(30);※デフォルト設定だとdotnet_dllディレクトリにあるfibonacci数のサンプルコードを呼び出すようになっている(iniで変更可)・実行docker compose exec php_fpm /bin/bash -c "cd /var/www && ./run.sh mycode.php"・その他サンプルコード./ext_test.php … 各種メソッドの動作サンプル./benchmarker/runbench.php … フィボナッチのベンチマーク
CoreCLRって?
CoreCLRって?・.NETの実行環境ほかのC/C++のプログラムに.NETのVM(※CLI)を組み込むことができるライブラリ(というかこれ自体で.NETを動かしている)dotnet_ffiはこれを使ってPHP ExtensionからC#を実行している※CLI…Common Language Infrastructure※CoreCLRリポジトリhttps://github.com/dotnet/coreclr/tree/v3.1.29・CoreFX、Mono Runtime等と類似の役割※参考: Embedding Monohttps://www.mono-project.com/docs/advanced/embedding/
CoreCLRの使い方(初期化)・libcoreclr.soをdlopenするcoreClr = dlopen(coreClrPath.c_str(), RTLD_NOW | RTLD_LOCAL);・dlsymでそれぞれのシンボルのアドレスを取得initializeCoreClr = (coreclr_initialize_ptr)dlsym(coreClr, "coreclr_initialize");createManagedDelegate = (coreclr_create_delegate_ptr)dlsym(coreClr,"coreclr_create_delegate");shutdownCoreClr = (coreclr_shutdown_ptr)dlsym(coreClr, "coreclr_shutdown");
CoreCLRの使い方(初期化)・CoreCLR初期化CoreCLRにpublishされたC#の.dllファイルパスを「:」区切りでpropertyValuesに一通りわたして初期化しますint hr = initializeCoreClr(runtimePath, // App base path"HostingOnPHP", // AppDomain friendly namesizeof(propertyKeys) / sizeof(char*), // Property countpropertyKeys, // Property namespropertyValues, // Property values&hostHandle, // Host handle&domainId); // AppDomain IDdotnet_ffiではPHPプロセスが作られるタイミングで初期化PHP_MINIT_FUNCTION(dotnet_ffi)
CoreCLRの使い方(C#呼び出し)・C#のメソッドの関数ポインタを取得C#のプロジェクト名、名前空間までつけた完全修飾のクラス名、メソッド名とC#のクラスへのアドレスが格納されるポインタを渡す*hr = createManagedDelegate (hostHandle,domainId,target_project_name .c_str(),target_class_name.c_str(),method_name,(void**)&managedDelegateInvokeReturnString);・C#のメソッド呼び出しstd::string ret = managedDelegateInvokeReturnString(inStr);この関数ポインタを実行することでC#のメソッドを呼び出せる
CoreCLRの使い方(終了)・C#のCoreCLRの終了使い終わったらshutdowndotnet_ffiではphpプロセスが作り直されるタイミングにあわせてシャットダウン(VMの生成と初回JITコンパイルコストが高いので)int hr = shutdownCoreClr(hostHandle, domainId);dotnet_ffiではPHPのプロセスが終了するタイミングPHP_MSHUTDOWN_FUNCTION(dotnet_ffi)
Extensionを作るための情報源
Extensionについての公式ドキュメント公式のExtensionのドキュメントはない(2022年9月時点)以前は「PHP のコア: ハッカーの手引き」という項目があったが無くなっているそれもPHP5.3.3頃で止まっている模様検索しても非公式のミラーがいくつか引っ掛かる程度(それもTLS対応していないサイトだったりする)
PHPのソースを追いましょう現時点で一番確実なのはPHPのソースを追うPHPのソースにあたることで下記の情報が得られる・Extensionのひな形の作り方・Extensionとしてのクラスや関数の宣言のしかた・PHP用のconfig.m4の書き方※ソースを追うときには使いたい PHPバージョンのタグを指定して追うのが安全
Extensionのひな形の作り方そもそもどこからExtension開発に手を付けていいかがわかりづらいがPHPのソース内のextディレクトリを探すとext_skel.phpというスクリプトがあり、これでひな形が作れるhttps://github.com/php/php-src/blob/php-8.1.10/ext/ext_skel.phpphp ext_skel.php --helpとするとExtensionのひな形の作り方やビルドのしかたがでてくる
Extensionのひな形の作り方extension_exampleというExtensionを作る場合の例php ext_skel.php --ext extension_examplecd extension_examplephpize./configuremakemake test※PECLでXMLを書いてひな形にする方法もありますここに作りたいExtension名を指定する
Extensionとしてのクラスや関数の宣言のしかた旧公式ドキュメントでもExtensionの関数の宣言のやり方は載っていたがExtensionとしてのクラスの宣言方法は載っていなかったPHPの標準クラスで似たようなインターフェイスのクラスを探してその書き方を参考にするのが楽同じextディレクトリ内にあるので色々見てみると参考になる(個人的にはSQLite3のクラスが一通りそろっていて参考になった)
PHP用config.m4の書き方ライブラリのリンクのしかたなど、automakeから拡張されているものもある例えばライブラリをstatic linkする際の書き方などは、ほかのExtensionを調べたほうが確実(一番調べるのに苦労した)例PHP_ADD_LIBRARY_WITH_PATH("coreclr_ctlpp", $DOTNET_FFI_DIR/coreclrhost, EXTRA_LDFLAGS)
まとめ
まとめ・PHPからC#のクラスが呼べるUnityなどのC#で動くクライアントとの親和性が向上できる・手軽に処理速度の向上が見込めるExtensionを書かなくてもC#などで高速化したい処理を書くだけでOK・PHPのソースを追うの大事Extensionを作るときにはもちろん標準のクラスや関数の細かい挙動の理解の助けになる
今後の展望
今後の展望・.NET7への対応.NET7自体が速度向上しているのもあり、間接的なPHPの処理速度向上が期待できそう・呼び出しメソッドを可変化C#側のメソッド名をPHPから動的に指定して実行できるようにしたい但し、パフォーマンスが若干犠牲になる可能性あるので慎重にゆきたい(現在はiniで指定する形、一度見つけた関数ポインタは使いまわし)・PHP8.2への対応リリースされ次第対応予定
要望等お気軽にお寄せください
おまけ: エンジニア系VtuberはじめましたTwitter@SabanoIrikoYoutube 佐波野いりこチャンネルhttps://www.youtube.com/channel/UCQoB5I4tj36wgcvzIQrjpVA↑よろしければチャンネル登録よろしくお願いします_(._.)_