TEL:400-8793-956
当前位置:网站讲堂

如何在编程中使用 PHP FFI

提问者: 近期获赞: 浏览人数: 发布时间:2021-12-06 12:10:51

 PHP 7.4的 2020 版本使开发人员能够做一些他们以前从未做过的事情——使用纯PHP 代码 访问数据结构和调用用另一种语言编写的函数,不需要扩展,也不需要绑定到外部库。这怎么可能? 使用 PHP FFI(外来函数接口)。 在本文中,我们将讨论 PHP FFI 是什么、它的优点和功能,并比较 PHP 如何在不需要创建插件的情况下与Go、Rust和 C++等语言一起工作。我们还将分享我们在实现此功能时使用的实验,并强调我们发现它最有用的地方以及我们认为不值得麻烦的地方。

 
什么是 PHP FFI?
通俗的说,FFI是一种接口,可以让开发者使用一种语言调用另一种语言编写的库函数。例如,FFI 使得从纯 PHP 调用用 Rust、C++ 或 Go 编写的函数成为可能。为了将解释型语言与编译型语言连接起来,使用了 libffi [ GitHub ] 库 - Wiki。 
 
由于解释型语言不知道具体到哪里去寻找被调用函数的参数(即在哪些寄存器中),也不知道从哪里得到调用后的函数结果,所以依赖于Libffi来做这项工作。因此,您需要安装此库,因为它是系统库 (Linux) 的一部分。
 
PHP FFI 的好处
虽然现在完全是实验性的,但 PHP FFI 的早期测试揭示了许多好处,这些好处可能会消除一些繁琐的 PHP 扩展,并最终迎来一个有趣的开发新时代。
 
节省时间和精力,不必编写 PHP 特定的扩展/模块来与 C 程序/库交互
在图像和视频处理等繁重的计算工作上执行得更快
与启动昂贵的 VM 和容器相比,在通用云平台上启动 PHP 实例更省钱
PHP FFI 实验
注意:所有 PHP FFI 实验均在 ArchLinux(5.6.1 内核)、Libffi 3.2.1 上进行。
 
虽然我们的实验并没有以非常严格的方式遵循科学方法,但我们确实是有目的的。探索新的语言特性当然很有趣,但我们问自己的问题是,这对软件开发有实际意义吗?结果如下:
 
 
使用 PHP FFI 计算斐波那契数列
对于我们的第一个实验,我们认为像计算斐波那契数列这样的问题既简单又有趣。当然,我们并没有打算以最有效的方式做到这一点。相反,我们想借助递归来尽可能多地使用处理器。这也会阻止编译语言优化此功能(例如,应用循环展开技术)。
 
实验 1:使用 Rust
对于 PHP,我们做的第一件事是取消注释php .ini 中的扩展名 ffi (/etc/php/php.ini在 ArchLinux 中)。
 
接下来,我们需要声明我们的条件接口。有一些限制,当前存在于PHP FFI,特别是不能使用C-预处理(#include,#define,等),除了一些特殊的。在 PHP 类型中:
 
$ffi = FFI::cdef(
     "int Fib(int n);",
    "/PATH/TO/SO/lib.so");
FFI::cdef – 通过这个操作,我们定义了交互界面。
int Fib (int n)– 它是编译语言的导出方法的名称。稍后我们将讨论如何正确地做到这一点。
/PATH/TO/SO/lib.so – 上面函数所在的动态库的路径。
我们使用的完整 PHP 脚本:
 
PHP FFI
 
function fib($n)
{
    if ($n === 1 || $n === 2) {
        return 1;
    }
    return fib($n - 1) + fib($n - 2);
}
 
$start = microtime(true);
$p = 0;
for ($i = 0; $i < 1000000; $i++) {
    $p = fib(12);
}
 
echo '[PHP] execution time: '.(microtime(true) - $start).' Result: '.$p.PHP_EOL;
铁锈FFI
 
$rust_ffi = FFI::cdef(
    "int Fib(int n);",
    "lib/libphp_rust_ffi.so");
 
$start = microtime(true);
$r = 0;
for ($i=0; $i < 1000000; $i++) { $r = $rust_ffi->Fib(12);
}
 
echo '[RUST] execution time: '.(microtime(true) - $start).' Result: '.$r.PHP_EOL;
CPP FFI
 
$cpp_ffi = FFI::cdef(
    "int Fib(int n);",
    "lib/libphp_cpp_ffi.so");
 
$start = microtime(true);
$c = 0;
for ($i=0; $i < 1000000; $i++) { $c = $cpp_ffi->Fib(12);
}
 
echo '[CPP] execution time: '.(microtime(true) - $start).' Result: '.$c.PHP_EOL;
GOLAN FFI
 
$golang_ffi = FFI::cdef(
    "int Fib(int n);",
    "lib/libphp_go_ffi.so");
 
$start = microtime(true);
 
for ($i=0; $i < 1000000; $i++) { $golang_ffi->Fib(12);
}
 
echo '[GOLANG] execution time: '.(microtime(true) - $start).' Result: '.$c.PHP_EOL;
第一步是用 Rust 语言制作一个动态库。
 
这需要一些准备:
 
1.在任何平台上,我们只需要从这里安装一个指令。
 
2. 之后,我们可以使用命令在任何地方创建一个项目。就是这样!cargo new rust_php_ffi
 
这是我们使用的函数:
 
锈:
 
//src/lib.rs
 
#[no_mangle]
extern "C" fn Fib(n: i32) -> i32 {
    if (n == 0) || (n == 1) {
        return 1;
    }
 
    Fib(n - 1) + Fib(n - 2)
}
注:这是至关重要的属性#[no_mangle]添加到所需要的功能,否则编译器会喜欢的东西取代您的函数的名称:_аgs @ fs34。并且在将其导出到 PHP 时,libffi 根本无法在动态库中找到名为 Fib 的函数。您可以在此处阅读有关此问题的更多信息。
 
在 Cargo.toml 中,我们添加了属性:
 
[lib]
crate-type = ["cdylib"]
我想提请您注意一个事实,即通过 Cargo.toml 中的一个属性,动态库有三个选项:
 
1. dylib– Rust 与一个不稳定的 ABI 共享这个库,它可以从一个版本到另一个版本(就像在 Go 内部 ABI 中一样)。
 
2.cdylib是一个在C/C++中使用的动态库。这是我们的首选。
 
3. rlib– 带有 rlib extestion ( .rlib) 的Rust 静态库。它还包含用于链接分别用 Rust 编写的各种 rlib 的元数据
 
然后,我们使用cargo build --release. 在文件夹中,target/release我们看到了该.so文件。这将是我们的动态库。
C++
 
接下来是 C++。这里的一切也很简单:
 
CPP:
 
// in php_cpp_ffi.cpp
 
int main() {
    
}
 
extern "C" int Fib(int n) {
    if ((n==1) || (n==2)) {
        return 1;
    }
 
    return Fib(n-1) + Fib(n-2);
}
我们需要声明该extern函数,以便它可以从 php 导入。
 
我们编译:
 
g++ -fPIC -O3 -shared src/php_cpp_ffi.cpp -o ../lib/ libphp_cpp_ffi.so
关于编译的几点意见:
 
1. -fPIC位置无关代码。对于动态库,重要的是独立于它在内存中加载的地址。
 
2. -O3– 最大优化
 
实验二:使用 Golang
我们实验的下Golang一个是——一种具有运行时的语言。为 Go 开发了一种与动态库交互的特殊机制,称为CGO。在此处了解有关此机制如何工作的更多信息。由于 CGO 会解释从 C 生成的错误,因此无法像在 C++ link和link 中那样使用优化。鼓声,请。. . 这是代码! 高朗:
package main
 
import (
        "C"
)
 
// we needed to have empty main in package main :)
// because -buildmode=c-shared requires exactly one main package
func main() {
 
}
 
//export Fib
func Fib(n C.int) C.int {
        if n == 1 || n == 2 {
                return 1
        }
 
        return Fib(n-1) + Fib(n-2)
}
因此,所有这些都是相同的 Fib 函数,但是,为了将此函数导出到动态库中,我们需要添加上面的注释(一种 GO 属性)//export Fib。然后,我们编译:go build -o ../lib/libphp_go_ffi.so -buildmode=c-shared. 注意我们如何添加 -buildmode = c-shared以获得动态库。我们在输出时收到了 2 个文件。一个带有标题的文件.h,.so是一个动态库。我们并不真正需要带有头文件的文件,因为我们知道函数的名称,而且 FFI PHP 在使用 C 预处理器时相当有限。
火箭发射
在我们写完所有内容(提供了源代码)之后,我们制作了一个小的 Makefile 来收集所有内容(它也位于存储库中)。我们叫后make build的在lib文件夹中,4个文件出现。两个用于 GO (.h/.so),一个用于 Rust 和 C++。
 
生成文件:
 
build_cpp:
        echo 'Building cpp...'
        cd cpp && g++ -fPIC -O3 -shared src/php_cpp_ffi.cpp -o libphp_cpp_ffi.so
 
build_go:
        echo 'Building golang...'
        cd golang && go build -o libphp_go_ffi.so -buildmode=c-shared
 
build_rust:
        echo 'Building Rust...'
        cargo build --release && mv rust/target/release/libphp_ffi.so libphp_rust_ffi.so
 
build: build_cpp build_go build_rust
 
 
run:
        php php/php_fib.php
然后,我们转到该php文件夹并运行我们的脚本(或通过 Makefile – make run)。注意:在 php 脚本中FFI::cdef,.so文件路径是硬编码的,所以为了一切正常,请运行make run. 工作结果如下:
 
1.[PHP]执行时间:8.6763260364532结果:144
 
2.[RUST]执行时间:0.32162690162659结果:144
 
3.[CPP]执行时间:0.3515248298645结果:144
 
4.[GOLANG]执行时间:5.0730509757996结果:144
 
不出所料,PHP在加载的CPU中显示出最低的结果数学运算,但总的来说,一百万次调用感觉相当快。
 
我们很惊讶 CGO 的运行时间比 PHP 少一点。这是由于calling-conventionsABI 不稳定造成的。CGO被迫进行Go-types到C(h构建GO动态库后得到的文件中可以看到)类型的类型转换操作,我们不得不复制C和GO的传入和返回值兼容性。
 
Rust 和 C++ 在我们的实验中显示出最好的结果,老实说,这正是我们的预期,因为它们具有稳定的 ABI(Rust 中的 extern)并且 PHP 和这些语言之间的唯一层是 libffi。
 
结论
由于遇到各种陷阱的可能性很大,PHP FFI 方法尚未准备好投入生产,但它很有希望。PHP 开发社区甚至在PHP.net上发布了他们自己的警告,在那里他们介绍了该功能。
 
上一篇: 如何运行可用性测试:用户体验设计师的快速指南
下一篇: 返回列表
城市网站导航:无锡网站建设 徐州企业网站定制 常州网站开发哪家好 苏州网站改版 南通网站开发 连云港网站建设 淮安网站建设公司 盐城网站定制 扬州网站改版公司 镇江网站开发 泰州高端网站制作 宿迁高端网站开发 江阴高端网站开发 宜兴建设网站 新沂建设网站 邳州建设网站 常熟建设网站 张家港网站改版 昆山公司网站开发 太仓高端网站制作 丹阳高端网站制作 句容专业建站 泰兴网站制作公司 合肥网站建设 芜湖网站制作 蚌埠网站建设 淮南网站制作哪家好 马鞍山网站建设 铜陵网站制作公司 安庆网站建设公司 黄山网站制作 滁州网站建设 阜阳网站设计 六安网站建设 亳州网站制作 宣城网站建设 巢湖网站建设哪家好 桐城网站制作 天长网站建设 明光网站建设 全椒网站建设 扬中网站建设公司 城市小程序导航: