ホームページ >バックエンド開発 >PHPチュートリアル >Rust_PHP チュートリアルを使用して PHP 拡張機能を作成する
昨年 10 月、私は Etsy の同僚と、現時点では PHP よりも Ruby や Python のようなインタープリター型言語の拡張機能を作成する方法について話し合いました。正常な拡張機能を作成する上で障壁となるのは、通常は C で作成する必要があることですが、C が苦手な場合はその自信を持つのが難しいことについて説明しました。
それ以来、Rust で書くというアイデアを思いつき、ここ数日間試してきました。今朝やっと起動できました。
C または PHP での Rust
Rust FFF外部関数インターフェース)
リーリー
私はCかその他の出身です! ) で呼び出される Rust ライブラリを使用して分割します。次に何が起こるのかをわかりやすく説明します。コンパイルすると、.a、libhello_from_rust.a というファイルが得られます。これは独自の依存関係をすべて含む静的ライブラリであり、C プログラムをコンパイルするときにリンクして、後続の処理を実行できるようにします。注: コンパイル後、次の出力が得られます:
リーリー
これは、この依存関係を使用しない場合に、Rust コンパイラーがリンクするように指示するものです。
C から Rust を呼び出す
以下はヘッダーファイルです:
リーリー
これはかなり基本的なヘッダー ファイルであり、単純な関数の署名/定義を提供するだけです。次に、C プログラムを作成して使用する必要があります。
リーリー
次のコードを実行してコンパイルします:gcc -Wall -o hello_c hello.c -L /Users/jmcfarland/code/rust/php-hello-rust -lhello_from_rust -lSystem -lpthread -lc -lm
最後の -lSystem -lpthread -lc -lm は、Rust コンパイラが Rust ライブラリをコンパイルするときにそれらを提供できるように、これらの「ネイティブ アンティーク」にリンクしないように gcc に指示することに注意してください。
次のコードを実行すると、バイナリ ファイルを取得できます:
リーリー
美しい! C から Rust ライブラリを呼び出したところです。次に、Rust ライブラリがどのように PHP 拡張機能に組み込まれるかを理解する必要があります。phpからcを呼び出す
この部分を理解するのに時間がかかりました。このドキュメントは、php 拡張機能に関してはこの世で最高のものではありません。最良の部分は、php ソースがスクリプト ext_skel (主に「Extended Skeleton」の略) をバンドルして得られることです。つまり、必要な定型コードのほとんどを生成します。コードを実行するために、私は PHP ドキュメント「Extended Bones」を一生懸命勉強しました。
引用符で囲まれていない php ソースをダウンロードし、コードを php ディレクトリに書き込んで実行することで開始できます:
リーリー
これにより、php 拡張機能の作成に必要な基本的なスケルトンが生成されます。次に、拡張機能をローカルに保持したい場所にフォルダーを移動します。そしてを移動してください
.錆びのソース.rustライブラリ
.cヘッダー
同じディレクトリに入ります。したがって、次のようなディレクトリを確認する必要があります:
.
§── クレジット
§── 実験中
§── config.m4
§── config.w32
§── hello_from_rust.c
§── hello_from_rust.h
§── hello_from_rust.php
§── hello_from_rust.rs
§── libhello_from_rust.a
§── php_hello_from_rust.h
└── テスト
━── 001.phpt
これらのファイルについては、上記の php ドキュメントで詳しく説明しています。拡張ファイルを作成します。まず config.m4 を編集します。
説明は省略しますが、私の結果は次のとおりです:
リーリー
私が理解しているように、これらは基本的なマクロコマンドです。ただし、これらのマクロ コマンドに関するドキュメントは非常に貧弱です。たとえば、「PHP_ADD_LIBRARY_WITH_PATH」で検索しても、PHP チームによって書かれた結果はありません。この PHP_ADD_LIBRARY_PATH マクロは、誰かが PHP 拡張機能の静的ライブラリのリンクについて話していた前のスレッドで見つけました。コメント内の他の推奨マクロは、ext_skel を実行した後に生成されました。構成のセットアップが完了したので、実際に PHP スクリプトからライブラリを呼び出す必要があります。これを行うには、自動生成されたファイル hello_from_rust.c を変更する必要があります。まず、hello_from_rust.h ヘッダー ファイルを include コマンドに追加します。次に、confirm_hello_from_rust_compiled の定義メソッドを変更する必要があります。
<ol class="dp-c"><li class="alt"><span><span>#</span><span class="keyword">include</span><span> </span><span class="string">"hello_from_rust.h"</span><span> </span></span></li><li><span> </span></li><li class="alt"><span><span class="comment">// a bunch of comments and code removed...</span><span> </span></span></li><li><span> </span></li><li class="alt"><span>PHP_FUNCTION(confirm_hello_from_rust_compiled) </span></li><li><span>{ </span></li><li class="alt"><span>char *arg = NULL; </span></li><li><span>int arg_len, len; </span></li><li class="alt"><span>char *strg; </span></li><li><span> </span></li><li class="alt"><span><span class="keyword">if</span><span> (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, </span><span class="string">"s"</span><span>, &arg, &arg_len) == FAILURE) { </span></span></li><li><span><span class="keyword">return</span><span>; </span></span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span>hello_from_rust(<span class="string">"Jared (from PHP!!)!"</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span>len = spprintf(&strg, 0, <span class="string">"Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP."</span><span>, </span><span class="string">"hello_from_rust"</span><span>, arg); </span></span></li><li><span>RETURN_STRINGL(strg, len, 0); </span></li><li class="alt"><span>} </span></li></ol>
注意:我添加了hello_from_rust(“Jared (fromPHP!!)!”);。
现在,我们可以试着建立我们的扩展:
<ol class="dp-c"><li class="alt"><span><span>$ phpize </span></span></li><li><span>$ ./configure </span></li><li class="alt"><span>$ sudo make install </span></li></ol>
就是它,生成我们的元配置,运行生成的配置命令,然后安装该扩展。安装时,我必须亲自使用sudo,因为我的用户并不拥有安装目录的 php 扩展。
现在,我们可以运行它啦!
<ol class="dp-c"><li class="alt"><span><span>$ php hello_from_rust.php </span></span></li><li><span>Functions available in the test extension: </span></li><li class="alt"><span>confirm_hello_from_rust_compiled </span></li><li><span> </span></li><li class="alt"><span>Hello from Rust, Jared (from PHP!!)! </span></li><li><span>Congratulations! You have successfully modified ext/hello_from_rust/config.m4. Module hello_from_rust is now compiled into PHP. </span></li><li class="alt"><span>Segmentation fault: 11 </span></li></ol>
还不错,php 已进入我们的 c 扩展,看到我们的应用方法列表并且调用。接着,c 扩展已进入我们的 rust 库,开始打印我们的字符串。那很有趣!但是……那段错误的结局发生了什么?
正如我所提到的,这里是使用了 Rust 相关的 println! 宏,但是我没有对它做进一步的调试。如果我们从我们的 Rust 库中删除并返回一个 char* 替代,段错误就会消失。
这里是 Rust 的代码:
<ol class="dp-c"><li class="alt"><span><span>#![crate_type = </span><span class="string">"staticlib"</span><span>] </span></span></li><li><span> </span></li><li class="alt"><span>#![feature(libc)] </span></li><li><span>extern crate libc; </span></li><li class="alt"><span><span class="keyword">use</span><span> std::ffi::{CStr, CString}; </span></span></li><li><span> </span></li><li class="alt"><span>#[no_mangle] </span></li><li><span>pub extern <span class="string">"C"</span><span> fn hello_from_rust(name: *</span><span class="keyword">const</span><span> libc::c_char) -> *</span><span class="keyword">const</span><span> libc::c_char { </span></span></li><li class="alt"><span>let buf_name = unsafe { CStr::from_ptr(name).to_bytes() }; </span></li><li><span>let str_name = String::from_utf8(buf_name.to_vec()).unwrap(); </span></li><li class="alt"><span>let c_name = format!(<span class="string">"Hello from Rust, {}"</span><span>, str_name); </span></span></li><li><span> </span></li><li class="alt"><span>CString::<span class="keyword">new</span><span>(c_name).unwrap().as_ptr() </span></span></li><li><span>} </span></li></ol>
并变更 C 头文件:
<ol class="dp-c"><li class="alt"><span><span>#ifndef __HELLO </span></span></li><li><span>#define __HELLO </span></li><li><span><span class="keyword">const</span><span> char * hello_from_rust(</span><span class="keyword">const</span><span> char *name); </span></span></li><li><span>#<span class="keyword">endif</span><span> </span></span></li></ol>
还要变更 C 扩展文件:
<ol class="dp-c"><li class="alt"><span><span>PHP_FUNCTION(confirm_hello_from_rust_compiled) </span></span></li><li><span>{ </span></li><li class="alt"><span>char *arg = NULL; </span></li><li><span>int arg_len, len; </span></li><li class="alt"><span>char *strg; </span></li><li><span> </span></li><li class="alt"><span><span class="keyword">if</span><span> (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, </span><span class="string">"s"</span><span>, &arg, &arg_len) == FAILURE) { </span></span></li><li><span><span class="keyword">return</span><span>; </span></span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span>char *str; </span></li><li><span>str = hello_from_rust(<span class="string">"Jared (from PHP!!)!"</span><span>); </span></span></li><li class="alt"><span>printf(<span class="string">"%s/n"</span><span>, str); </span></span></li><li><span> </span></li><li class="alt"><span>len = spprintf(&strg, 0, <span class="string">"Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP."</span><span>, </span><span class="string">"hello_from_rust"</span><span>, arg); </span></span></li><li><span>RETURN_STRINGL(strg, len, 0); </span></li><li class="alt"><span>} </span></li></ol>
无用的微基准
那么为什么你还要这样做?我还真的没有在现实世界里使用过这个。但是我真的认为斐波那契序列算法就是一个好的例子来说明一个PHP拓展如何很基本。通常是直截了当在Ruby中):
<ol class="dp-c"><li class="alt"><span><span>def fib(at) </span><span class="keyword">do</span><span> </span></span></li><li><span><span class="keyword">if</span><span> (at == 1 || at == 0) </span></span></li><li class="alt"><span><span class="keyword">return</span><span> at </span></span></li><li><span><span class="keyword">else</span><span> </span></span></li><li class="alt"><span><span class="keyword">return</span><span> fib(at - 1) + fib(at - 2) </span></span></li><li><span><span class="func">end</span><span> </span></span></li><li class="alt"><span><span class="func">end</span><span> </span></span></li></ol>
而且可以通过不使用递归来改善这不好的性能:
<ol class="dp-c"><li class="alt"><span><span>def fib(at) </span><span class="keyword">do</span><span> </span></span></li><li><span><span class="keyword">if</span><span> (at == 1 || at == 0) </span></span></li><li class="alt"><span><span class="keyword">return</span><span> at </span></span></li><li><span>elsif (val = @cache[at]).present? </span></li><li class="alt"><span><span class="keyword">return</span><span> val </span></span></li><li><span><span class="func">end</span><span> </span></span></li><li class="alt"><span> </span></li><li><span>total = 1 </span></li><li class="alt"><span>parent = 1 </span></li><li><span>gp = 1 </span></li><li class="alt"><span> </span></li><li><span>(1..at).each <span class="keyword">do</span><span> |i| </span></span></li><li class="alt"><span>total = parent + gp </span></li><li><span>gp = parent </span></li><li class="alt"><span>parent = total </span></li><li><span><span class="func">end</span><span> </span></span></li><li class="alt"><span> </span></li><li><span><span class="keyword">return</span><span> total </span></span></li><li class="alt"><span><span class="func">end</span><span> </span></span></li></ol>
那么我们围绕它来写两个例子,一个在PHP中,一个在Rust中。看看哪个更快。下面是PHP版:
<ol class="dp-c"><li class="alt"><span><span>def fib(at) </span><span class="keyword">do</span><span> </span></span></li><li><span><span class="keyword">if</span><span> (at == 1 || at == 0) </span></span></li><li class="alt"><span><span class="keyword">return</span><span> at </span></span></li><li><span>elsif (val = @cache[at]).present? </span></li><li class="alt"><span><span class="keyword">return</span><span> val </span></span></li><li><span><span class="func">end</span><span> </span></span></li><li class="alt"><span> </span></li><li><span>total = 1 </span></li><li class="alt"><span>parent = 1 </span></li><li><span>gp = 1 </span></li><li class="alt"><span> </span></li><li><span>(1..at).each <span class="keyword">do</span><span> |i| </span></span></li><li class="alt"><span>total = parent + gp </span></li><li><span>gp = parent </span></li><li class="alt"><span>parent = total </span></li><li><span><span class="func">end</span><span> </span></span></li><li class="alt"><span> </span></li><li><span><span class="keyword">return</span><span> total </span></span></li><li class="alt"><span><span class="func">end</span><span> </span></span></li><li><span> </span></li><li class="alt"><span>这是它的运行结果: </span></li><li><span> </span></li><li class="alt"><span>$ time php php_fib.php </span></li><li><span> </span></li><li class="alt"><span>real 0m2.046s </span></li><li><span>user 0m1.823s </span></li><li class="alt"><span>sys 0m0.207s </span></li><li><span> </span></li><li class="alt"><span>现在我们来做Rust版。下面是库资源: </span></li><li><span> </span></li><li class="alt"><span>#![crate_type = <span class="string">"staticlib"</span><span>] </span></span></li><li><span> </span></li><li class="alt"><span>fn fib(at: usize) -> usize { </span></li><li><span><span class="keyword">if</span><span> at == 0 { </span></span></li><li class="alt"><span><span class="keyword">return</span><span> 0; </span></span></li><li><span>} <span class="keyword">else</span><span> </span><span class="keyword">if</span><span> at == 1 { </span></span></li><li class="alt"><span><span class="keyword">return</span><span> 1; </span></span></li><li><span>} </span></li><li class="alt"><span> </span></li><li><span>let mut total = 1; </span></li><li class="alt"><span>let mut parent = 1; </span></li><li><span>let mut gp = 0; </span></li><li class="alt"><span><span class="keyword">for</span><span> _ in 1 .. at { </span></span></li><li><span>total = parent + gp; </span></li><li class="alt"><span>gp = parent; </span></li><li><span>parent = total; </span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span><span class="keyword">return</span><span> total; </span></span></li><li><span>} </span></li><li class="alt"><span> </span></li><li><span>#[no_mangle] </span></li><li class="alt"><span>pub extern <span class="string">"C"</span><span> fn rust_fib(at: usize) -> usize { </span></span></li><li><span>fib(at) </span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span>注意,我编译的库rustc – O rust_lib.rs使编译器优化因为我们是这里的标准)。这里是C扩展源相关摘录): </span></li><li><span> </span></li><li class="alt"><span>PHP_FUNCTION(confirm_rust_fib_compiled) </span></li><li><span>{ </span></li><li class="alt"><span>long number; </span></li><li><span> </span></li><li class="alt"><span><span class="keyword">if</span><span> (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, </span><span class="string">"l"</span><span>, &number) == FAILURE) { </span></span></li><li><span><span class="keyword">return</span><span>; </span></span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span>RETURN_LONG(rust_fib(number)); </span></li><li><span>} </span></li></ol>
运行PHP脚本:
<ol class="dp-c"><li class="alt"><span><span><?php </span></span></li><li><span><span class="vars">$br</span><span> = (php_sapi_name() == </span><span class="string">"cli"</span><span>)? </span><span class="string">""</span><span>:</span><span class="string">"<br>"</span><span>; </span></span></li><li class="alt"><span> </span></li><li><span><span class="keyword">if</span><span>(!</span><span class="func">extension_loaded</span><span>(</span><span class="string">'rust_fib'</span><span>)) { </span></span></li><li class="alt"><span>dl(<span class="string">'rust_fib.'</span><span> . PHP_SHLIB_SUFFIX); </span></span></li><li><span>} </span></li><li class="alt"><span> </span></li><li><span><span class="keyword">for</span><span> (</span><span class="vars">$i</span><span> = 0; </span><span class="vars">$i</span><span> < 100000; </span><span class="vars">$i</span><span> ++) { </span></span></li><li class="alt"><span>confirm_rust_fib_compiled(92); </span></li><li><span>} </span></li><li class="alt"><span>?> </span></li><li><span> </span></li><li class="alt"><span>这就是它的运行结果: </span></li><li><span> </span></li><li class="alt"><span>$ time php rust_fib.php </span></li><li><span> </span></li><li class="alt"><span>real 0m0.586s </span></li><li><span>user 0m0.342s </span></li><li class="alt"><span>sys 0m0.221s </span></li></ol>
你可以看见它比前者快了三倍!完美的Rust微基准!
总结
这里几乎没有得出什么结论。我不确定在Rust上写一个PHP的扩展是一个好的想法,但是花费一些时间去研究Rust,PHP和C,这是一个很好的方式。
如果你希望查看所有代码或者查看更改记录,可以访问GitHub Repo。