Loading... PHP外部函数接口:FFI,是一个PHP扩展,允许您轻松地将一些外部库包含到PHP代码中。这意味着可以直接在PHP中使用C,Go,Rust等共享库,而无需在C中编写PHP扩展。这个概念在其他语言(如Python或Go)中已经存在多年了。 UUID生成 让我们从一个小例子开始:UUID生成。 # [PHP 7.4 FFI(外部函数接口)](https://blog.p2hp.com/archives/6947) 使用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 <a class="wpal-linked-keyword" href="http://symfony.p2hp.com/" target="_blank">symfony</a>/crawler: Duration: 2.321s 我们可以轻松得出结论,如果计算复杂,将一部分PHP代码移植到另一种语言以提高应用程序性能可能非常有趣。 结论 FFI是个好东西。即使绑定尚不存在,FFI也会允许我们尝试一些库。它将使我们可以用Rust(例如)植入替换代码的某些慢速部分。而且我敢肯定,它将解锁一些我们还没有的想法。 最后修改:2023 年 08 月 11 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏