Slide 1

Slide 1 text

PHPからC#のライブラリを 呼べるようにしたdotnet_ffiを 趣味でつくってみた よーがす @pg_ito (アバター: 佐波野いりこ @SabanoIriko) PHP Conference Japan 2022 09/25

Slide 2

Slide 2 text

自己紹介 ● エンジニア 最近は主にPHP、C#、たまにC++、JavaScript サーバの負荷対策が好き ● 趣味 配信、電子工作、スプラトゥーン3 名前: よーがす Twitter: @pg_ito Blog: https://b64.pw/blog/

Slide 3

Slide 3 text

もくじ ● dotnet_ffiってなに? ● 作った動機 ● 想定される利用ケース ● dotnet_ffiの構成 ● dotnet_ffiの簡単な使い方 ● CoreCLRって? ● Extensionを作るための情報源 ● まとめ ● 今後の展望

Slide 4

Slide 4 text

dotnet_ffiってなに?

Slide 5

Slide 5 text

dotnet_ffiってなに? PHPからC#のライブラリを直接呼べるようにしたPHP Extension CoreCLRをつかってC#のDLLを動的にロード、実行している PHPプロセスが作り直されるまでC#のVMを使いまわすので C#のJITの恩恵を得られて高速に動作 リポジトリ: https://github.com/pg-ito/dotnet_ffi (MITライセンス)

Slide 6

Slide 6 text

パフォーマンス比較 fibonacci数を計算させた場合PHPのみに比べて約16倍ほど高速(PHP8.1.6の場合)

Slide 7

Slide 7 text

作った動機

Slide 8

Slide 8 text

作った動機 ・Unityと同じ処理をPHPサーバ側でも行いたい ・Extensionを書かずに手軽にPHPの処理を高速化したい ・C#にしかない機能を使いたい

Slide 9

Slide 9 text

想定される利用ケース

Slide 10

Slide 10 text

UnityClientと処理の共通化 ・ゲームのチート対策 クライアントから送られてきたデータが不正なものでないか 正規のクライアント側のC#の処理と比較できる ・バリデータの共通化 APIのエラーチェックなどを一元化できる

Slide 11

Slide 11 text

処理の高速化 ・CPU使用率の高い処理をC#側で高速に行える 例えばfibonacci数を求めるような処理で効果を発揮 (リポジトリ内のベンチマークにこのケースを想定したコードを同梱)

Slide 12

Slide 12 text

C#にしかないライブラリの利用 ・Linqでのデータの抽出 多次元配列を任意の形に整形 (おそらくPHPのみで書かれている限りPHP移植版Linqにくらべて高速な…ハズ)

Slide 13

Slide 13 text

dotnet_ffiの構成

Slide 14

Slide 14 text

PHP Process 設計の特徴 ・PHP ExtensionがC/C++で書けることを利用してCoreCLR経由でC#とバインド PHP ➡ dotnet_ffi(C,C++) ➡ CoreCLR(C++) ➡ C# Library PHP 標準 Extension CoreCLR (C++) C# Library PHP 標準関数等 PHP 言語 構造 dotnet_ffi (C, C++) ※CoreCLRについては後述

Slide 15

Slide 15 text

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) 型情報を追加

Slide 16

Slide 16 text

dotnet_ffiの簡単な使い方

Slide 17

Slide 17 text

ビルド ・ソースのクローン git clone https://github.com/pg-ito/dotnet_ffi.git . ・docker composeでビルドと実行 cd build_env docker compose -up -d --build docker 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-devel yum install -y dotnet-sdk-6.0 ./br.sh

Slide 18

Slide 18 text

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 … フィボナッチのベンチマーク

Slide 19

Slide 19 text

CoreCLRって?

Slide 20

Slide 20 text

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 Mono https://www.mono-project.com/docs/advanced/embedding/

Slide 21

Slide 21 text

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");

Slide 22

Slide 22 text

CoreCLRの使い方(初期化) ・CoreCLR初期化 CoreCLRにpublishされたC#の.dllファイルパスを「:」区切りで propertyValuesに一通りわたして初期化します int hr = initializeCoreClr( runtimePath, // App base path "HostingOnPHP", // AppDomain friendly name sizeof(propertyKeys) / sizeof(char*), // Property count propertyKeys, // Property names propertyValues, // Property values &hostHandle, // Host handle &domainId); // AppDomain ID dotnet_ffiではPHPプロセスが作られるタイミングで初期化 PHP_MINIT_FUNCTION(dotnet_ffi)

Slide 23

Slide 23 text

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#のメソッドを呼び出せる

Slide 24

Slide 24 text

CoreCLRの使い方(終了) ・C#のCoreCLRの終了 使い終わったらshutdown dotnet_ffiではphpプロセスが作り直されるタイミングにあわせてシャットダウン (VMの生成と初回JITコンパイルコストが高いので) int hr = shutdownCoreClr(hostHandle, domainId); dotnet_ffiではPHPのプロセスが終了するタイミング PHP_MSHUTDOWN_FUNCTION(dotnet_ffi)

Slide 25

Slide 25 text

Extensionを作るための情報源

Slide 26

Slide 26 text

Extensionについての公式ドキュメント 公式のExtensionのドキュメントはない(2022年9月時点) 以前は「PHP のコア: ハッカーの手引き」という項目があったが無くなっている それもPHP5.3.3頃で止まっている模様 検索しても非公式のミラーがいくつか引っ掛かる程度 (それもTLS対応していないサイトだったりする)

Slide 27

Slide 27 text

PHPのソースを追いましょう 現時点で一番確実なのはPHPのソースを追う PHPのソースにあたることで下記の情報が得られる ・Extensionのひな形の作り方 ・Extensionとしてのクラスや関数の宣言のしかた ・PHP用のconfig.m4の書き方 ※ソースを追うときには使いたい PHPバージョンのタグを指定して追うのが安全

Slide 28

Slide 28 text

Extensionのひな形の作り方 そもそもどこからExtension開発に手を付けていいかがわかりづらいが PHPのソース内のextディレクトリを探すと ext_skel.phpというスクリプトがあり、これでひな形が作れる https://github.com/php/php-src/blob/php-8.1.10/ext/ext_skel.php php ext_skel.php --help とするとExtensionのひな形の作り方やビルドのしかたがでてくる

Slide 29

Slide 29 text

Extensionのひな形の作り方 extension_exampleというExtensionを作る場合の例 php ext_skel.php --ext extension_example cd extension_example phpize ./configure make make test ※PECLでXMLを書いてひな形にする方法もあります ここに作りたいExtension名を指定する

Slide 30

Slide 30 text

Extensionとしてのクラスや関数の宣言のしかた 旧公式ドキュメントでもExtensionの関数の宣言のやり方は載っていたが Extensionとしてのクラスの宣言方法は載っていなかった PHPの標準クラスで似たようなインターフェイスのクラスを探して その書き方を参考にするのが楽 同じextディレクトリ内にあるので色々見てみると参考になる (個人的にはSQLite3のクラスが一通りそろっていて参考になった)

Slide 31

Slide 31 text

PHP用config.m4の書き方 ライブラリのリンクのしかたなど、automakeから拡張されているものもある 例えばライブラリをstatic linkする際の書き方などは、ほかのExtensionを調べたほうが 確実(一番調べるのに苦労した) 例 PHP_ADD_LIBRARY_WITH_PATH("coreclr_ctlpp", $DOTNET_FFI_DIR/coreclrhost, EXTRA_LDFLAGS)

Slide 32

Slide 32 text

まとめ

Slide 33

Slide 33 text

まとめ ・PHPからC#のクラスが呼べる UnityなどのC#で動くクライアントとの親和性が向上できる ・手軽に処理速度の向上が見込める Extensionを書かなくてもC#などで高速化したい処理を書くだけでOK ・PHPのソースを追うの大事 Extensionを作るときにはもちろん 標準のクラスや関数の細かい挙動の理解の助けになる

Slide 34

Slide 34 text

今後の展望

Slide 35

Slide 35 text

今後の展望 ・.NET7への対応 .NET7自体が速度向上しているのもあり、 間接的なPHPの処理速度向上が期待できそう ・呼び出しメソッドを可変化 C#側のメソッド名をPHPから動的に指定して実行できるようにしたい 但し、パフォーマンスが若干犠牲になる可能性あるので慎重にゆきたい (現在はiniで指定する形、一度見つけた関数ポインタは使いまわし) ・PHP8.2への対応 リリースされ次第対応予定

Slide 36

Slide 36 text

要望等お気軽にお寄せください

Slide 37

Slide 37 text

おまけ: エンジニア系Vtuberはじめました Twitter @SabanoIriko Youtube 佐波野いりこチャンネル https://www.youtube.com/channel/UCQoB5I4tj36wgcvzIQrjpVA ↑よろしければチャンネル登録 よろしくお願いします_(._.)_