PHP外部函数接口:FFI,是一个PHP扩展,允许您轻松地将一些外部库包含到PHP代码中。这意味着可以直接在PHP中使用C,Go,Rust等共享库​​,而无需在C中编写PHP扩展。这个概念在其他语言(如Python或Go)中已经存在多年了。

UUID生成

让我们从一个小例子开始:UUID生成。

使用PHP,有几种生成UUID的方法。最好的方法是使用PECL UUID扩展名。您可以在GitHub上阅读其代码。这个PHP扩展负责将PHP函数绑定到libuuid。要使其正常工作,您必须在系统上安装libuuid(不必担心,几乎总是这样)和PECL。

这就是我们从PHP用户代码调用uuid_create()时发生的情况:

+---------------------+
   |    your PHP code    |
   +---+-------------^---+
       v             ^
   +---v-------------+---+
   |     PHP engine      |
   +---+-------------^---+
       v             ^
   +---v-------------+---+
   |      UUID ext       |
   +---+-------------^---+
       v             ^
   +---v-------------+---+
   |       UUID lib      |
   +---------------------+

FFI承诺用纯PHP代码替换“ UUID扩展”层。

在讨论PHP扩展或FFI层之前,我们需要解释什么是库。库通常是用C编写的。但是也可以用许多其他可以编译为共享库的语言来编写:C ++,Rust,Go等。在unix或linux上,该库将被编译成一个.so文件。在Windows上它将是一个.dll文件。也可以将库静态包含到二进制文件中,但是本章不在本文的讨论范围之内。

在库代码源中,有.h文件。它们包含库能够执行的操作。这是uuid.h文件的摘录:

# …
# Some constants:
#define UUID_VARIANT_NCS    0
#define UUID_VARIANT_DCE    1
#define UUID_VARIANT_MICROSOFT  2
#define UUID_VARIANT_OTHER  3

# Some function declarations:
void uuid_generate(uuid_t out);
int uuid_compare(const uuid_t uu1, const uuid_t uu2);
# …

一个.h文件类似于PHP接口的东西:它含有常量和函数签名。

FFI/UUID 层

为了工作,FFI 需要我们想要使用的基础库 (libuuid) 的函数签名。因此,我们将.h文件复制到我们的项目中。有时,您可以清理并调整此文件以满足您的需要。例如,您可以删除永远不会使用的函数。这就是我们的文件的样子:

#define FFI_LIB "libuuid.so.1"

typedef unsigned char uuid_t[16];

extern void uuid_generate_time(uuid_t out); // v1
extern void uuid_generate_md5(uuid_t out, const uuid_t ns, const char *name, size_t len); // v3
extern void uuid_generate_random(uuid_t out); // v4
extern void uuid_generate_sha1(uuid_t out, const uuid_t ns, const char *name, size_t len); // v5

这是最重要的,也是编写代码中更复杂的部分。完成后,我们可以将此文件包含在我们的PHP代码中:

$ffi = FFI::load(__DIR__ . '/include/uuid-php.h');

我们现在可以直接从PHP代码中使用libuuid。容易,不是吗?

但等一下,libuuid不能完全那样工作。所有函数都期望一些类型化的参数,您可能已经看到过。这些函数不返回 UUID,但将通过引用第一个参数进行修改。因此,在调用 函数之前,我们需要此值:

$output = $ffi->new('uuid_t');

$output是的实例FFI\CData。根据的内部类型,CData由于文档中描述了不同的运算符,我们可以访问不同的值。

最后,我们可以调用我们的函数。uuid_generate_random()匹配.h文件中库公开的名称:

$ffi->uuid_generate_random($output);

$output的内容将用组成 UUID 的十进制值数组进行更新。现在,我们需要将此数组转换为十六进制值的字符串:

foreach ($output as $values[]);

$uuid = sprintf('%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x', ...$values);

你喜欢它吗?如果您不想麻烦复制它,我们为您制作了它,并且它是开源的:https: //github.com/jolicode/ffi-uuid🍾

一些想法

简单

外部库的绑定确实很容易。最复杂的部分是创建最小 .h文件,并将PHP类型映射到库,反之亦然。

性能

看看我们实现的性能也很有趣。在我们的存储库中,您可以找到一个基准脚本。这是我们的实现与PECL之间进行比较的结果:

FFI:
 * [v1] 1.254s
 * [v4] 5.301s
PECL:
 * [v1] 0.626s
 * [v4] 4.583s

可以看到,PECL比我们的UUID V1实现快两倍,但对于UUID V4仅快15%。我们可以很容易地解释一下:UUID V4仅由伪随机数据组成,而UUID V1包含许多静态块。获取随机数据有点慢,这就是为什么V4生成要慢得多的原因。在V4上,这两种实现的区别不明显,因为几乎所有时间都花在了内部libuuid

我们可以得出什么结论?

FFI确实还很年轻(在撰写本文时甚至没有发布)。因此,我们可以期待一些性能上的改进。但是我们已经可以说:

  • 如果本机扩展已经存在并且可以安装,请使用它:
  • 如果扩展不存在,那么FFI是一个很好的候选项;
  • 如果应用程序中存在瓶颈,在 C、Rust 等中移植这些代码位并将其绑定到 FFI 可能很有趣。当 CPU 受限制时,FFI 会变得非常有趣:DOM 管理、大阵列、复杂计算等。

原来的扩展是否将替换为FFI?

现在说还真为时过早。但是,某些扩展(例如PDO)不仅仅具有简单绑定到库的功能。我非常有信心这些扩展将不会被FFI取代。

但是,某些扩展可能会被替换。php-redis,amqp,uuid等就是这种情况。例如Remi Collet已经开始使用FFI来代替redis扩展

FFI敞开了大门:可以替换一些纯PHP库,而改用低级库。gitlib可以使用带有FFI的libgit2就是这种情况。

在某些情况下,没有C扩展,也没有纯PHP实现。如果您曾经尝试在PHP中测试TensorFlow,您会知道它很复杂。Dmitry Stogov是最重要的PHP Core贡献者之一,也是PHP / FFI的作者,他创建了一个POC来将TensorFlow绑定到PHP

选择哪种语言将lib绑定到PHP?

能够编译到共享库(.so)的所有语言在系统上都不能很好地绑定到PHP。最好使用没有运行时的语言(C / C ++ / Rust /…),因为运行时可能会有副作用。例如,在GO中,运行时具有垃圾回收器并管理goroutine的线程。这可能会降低执行速度,甚至破坏您的应用程序。

如何将Rust lib绑定到PHP?

我想尝试一下用另一种语言比PHP执行复杂的计算是否更快。从网页提取HTML片段是很常见的:为了测试您的网站,或在爬网时。

Joel创建了一个小型库,用于提取与CSS表达式匹配的文档的第一个HTML元素。该代码确实很短,在某种程度上,Rust类型向C Type的转换代表了代码的2/3以上。

PHP绑定看起来与UUID非常相似。但是这里我们将.so直接包含在源代码中:

$ffi = FFI::cdef(<<<EOH
const char *cssfilter(const char *html, const char *filter);
EOH, __DIR__.'/../target/release/libcssfilter.so');

而且用法更简单:

$value = $ffi->cssfilter($html, $selector);

 

性能确实令人印象深刻,令人鼓舞:

FFI:
 Duration: 1.731s
symfony/crawler:
 Duration: 2.321s

我们可以轻松得出结论,如果计算复杂,将一部分PHP代码移植到另一种语言以提高应用程序性能可能非常有趣。

结论

FFI是个好东西。即使绑定尚不存在,FFI也会允许我们尝试一些库。它将使我们可以用Rust(例如)植入替换代码的某些慢速部分。而且我敢肯定,它将解锁一些我们还没有的想法。

来自https://jolicode.com/blog/php-7-4-ffi-what-you-need-to-know

PHP 7.4 FFI(外部函数接口):您需要知道的
标签: