在PHP程序中使用Rust扩展的方法

 百家乐-前端     |      2020-03-14 06:13

更新: 初稿刚宣布还不多个小时小编意识到作者的 PHP 基准测量试验是错的。为正义起见自个儿早已更新了 PHP 和 Rust 的本子。你能够在 GitHub 仓Curry见到変更(链接在尾巴部分)。

 C或PHP中的Rust

2018年1月,小编和 Etsy 的同事有过二个有关如何为像PHP样的解释性语言写拓宽的探究,Ruby或Python近日的光景相应会比PHP轻易。大家谈起了写叁个成功开创增加的阻碍是它们平常要求用C来写,不过若是你不专长C那门语言的话很难有不行信心。

本身的骨干观点就是写一些得以编写翻译的Rust代码到叁个Curry面,并写为它有些C的头文件,在C中为被调用的PHP做叁个进展。尽管实际不是超轻易,但是很有意思。
Rust FFI(foreign function interface)

自此时起笔者便萌生了用Rust写三个的主张,过去的几天平昔在品尝。前日早晨笔者算是让它运维了。

本身所做的率先件业务正是摆弄Rust与C连接的Rust的外界函数接口。笔者曾用轻松的主意(hello_from_rust)写过多少个灵活的库,伴有纯粹的评释(a pointer to a C char, otherwise known as a string),如下是输入后输出的“Hello from Rust”。  

C或PHP中的Rust

本人的骨干注重点就是写一些方可编写翻译的Rust代码到二个Curry面,并写为它有些C的头文件,在C中为被调用的PHP做三个开展。就算并不是非常的粗略,不过很风趣。

// hello_from_rust.rs
#![crate_type = "staticlib"]

#![feature(libc)]
extern crate libc;
use std::ffi::CStr;

#[no_mangle]
pub extern "C" fn hello_from_rust(name: *const libc::c_char) {
 let buf_name = unsafe { CStr::from_ptr(name).to_bytes() };
 let str_name = String::from_utf8(buf_name.to_vec()).unwrap();
 let c_name = format!("Hello from Rust, {}", str_name);
 println!("{}", c_name);
}

Rust FFI(foreign function interface)

自家所做的首先件业务便是摆弄Rust与C连接的Rust的外表函数接口。笔者曾用简易的办法(hello_from_rust)写过四个灵活的库,伴有单纯的扬言(a pointer to a C char, otherwise known as a string),如下是输入后输出的“Hello from Rust”。

// hello_from_rust.rs
#![crate_type = "staticlib"]

#![feature(libc)]
extern crate libc;
use std::ffi::CStr;

#[no_mangle]
pub extern "C" fn hello_from_rust(name: *const libc::c_char) {
    let buf_name = unsafe { CStr::from_ptr(name).to_bytes() };
    let str_name = String::from_utf8(buf_name.to_vec()).unwrap();
    let c_name   = format!("Hello from Rust, {}", str_name);
    println!("{}", c_name);
}

本人从C(或别的!)中调用的Rust库拆分它。这有一个接下去会怎么的很好的解释。

编写翻译它会取得.a的二个文书,libhello_from_rust.a。那是二个静态的库,包括它自身独具的依据关系,并且大家在编写翻译二个C程序的时候链接它,那让我们能做持续的事务。注意:在我们编译后会获得如下输出:

note: link against the following native artifacts when linking against this static library
note: the order and any duplication can be significant on some platforms, and so may need to be preserved
note: library: Systemnote: library: pthread
note: library: c
note: library: m

那就是Rust编写翻译器在咱们不选用那一个依附的时候所告诉大家必要链接什么。

自己从C(或任何!)中调用的Rust库拆分它。这有三个接下去会什么的很好的解说。

从C中调用Rust

既是大家有了多个库,不能不做两件事来保管它从C中可调用。首先,大家要求为它创设三个C的头文件,hello_from_rust.h。然后在大家编译的时候链接到它。

下边是头文件:

// hello_from_rust.h
#ifndef __HELLO
#define __HELLO

void hello_from_rust(const char *name);

#endif

那是四个优异基本功的头文件,仅仅为了叁个简约的函数提供签字/定义。接着大家需求写二个C程序并应用它。

// hello.c
#include <stdio.h>
#include <stdlib.h>
#include "hello_from_rust.h"

int main(int argc, char *argv[]) {
    hello_from_rust("Jared!");
}

咱俩因而运营一下代码来编译它:

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告诉gcc不要链接那个“当地的古物”,为了当编写翻译咱们的Rust库时Rust编写翻译器可以提供出来。

经运行上面包车型大巴代码大家得以获取二个二进制的文书:

$ ./hello_c
Hello from Rust, Jared!

至善至美!大家刚刚从C中调用了Rust库。以往大家必要驾驭Rust库是什么样步入一个PHP扩大的。

从 php 中调用 c

该部分花了自身有个别日子来弄驾驭,在此个世界上,该文书档案在 php 扩张中实际不是最棒的。最佳的有个别是出自绑定多少个本子 ext_skel 的 php 源(大许多表示“扩充骨架”)即生成大大多你必要的样子代码。为了让代码运维,小编可怜大力地读书 php 文书档案,“扩充骨骼”。

您能够经过下载来开始,和未分配的定额的 php 源,把代码写进 php 目录而且运维:

$ cd ext/
$ ./ext_skel –extname=hello_from_rust

这将转移必要创建 php 扩充的骨干骨架。以往,移动你处处想有些地维持你的扩展的文件夹。何况一举手一投足你的

.rust 源

.rust库

.c header

跻身同贰个目录。由此,未来您应该看看像这么的八个索引:

.
├── CREDITS
├── EXPERIMENTAL
├── 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
└── tests
└── 001.phpt

叁个目录,10个文件

您能够在 php docs 在上边看见关于那一个文件很好的汇报。建立贰个恢宏的文书。大家将经过编制config.m4 来以前吧。

不表达,上边正是自己的收获:

PHP_ARG_WITH(hello_from_rust, for hello_from_rust support,
[  --with-hello_from_rust             Include hello_from_rust support])

if test "$PHP_HELLO_FROM_RUST" != "no"; then
  PHP_SUBST(HELLO_FROM_RUST_SHARED_LIBADD)

  PHP_ADD_LIBRARY_WITH_PATH(hello_from_rust, ., HELLO_FROM_RUST_SHARED_LIBADD)

  PHP_NEW_EXTENSION(hello_from_rust, hello_from_rust.c, $ext_shared)
fi

正如小编所知道的那么,这几个是核心的宏命令。不过关于这么些宏命令的文书档案是特不好的(比如:google”PHP_ADD_LIBRARY_WITH_PATH”并未现身PHP团队所写的结果)。小编偶尔候那个PHP_ADD_LIBRARY_PATH宏命令在几人所争论的在叁个PHP拓宽里链接二个静态库的先前的线程里。在争辨中其余的引荐应用的宏命令是在自家运转ext_skel后产生的。

既是大家开展了计划安装,我们须要从PHP脚本中实际上地调用库。为此大家得更正自动生成的文书,hello_from_rust.c。首先我们加多hello_from_rust.h头文件到含有命令中。然后我们要校订confirm_hello_from_rust_compiled的定义方法。

#include "hello_from_rust.h"

// a bunch of comments and code removed...

PHP_FUNCTION(confirm_hello_from_rust_compiled)
{
    char *arg = NULL;
    int arg_len, len;
    char *strg;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
        return;
    }

    hello_from_rust("Jared (from PHP!!)!");

    len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "hello_from_rust", arg);
    RETURN_STRINGL(strg, len, 0);
}

只顾:笔者加多了hello_from_rust(“Jared (fromPHP!!)!”);。

几眼前,大家能够试着创建大家的扩展:

$ phpize
$ ./configure
$ sudo make install

就是它,生成大家的元配置,运维生成的配置命令,然后安装该增添。安装时,作者必须亲自使用sudo,因为笔者的客商并不享有安装目录的 php 扩充。

今后,大家得以运作它啦!

$ php hello_from_rust.php
Functions available in the test extension:
confirm_hello_from_rust_compiled

Hello from Rust, Jared (from PHP!!)!
Congratulations! You have successfully modified ext/hello_from_rust/config.m4. Module hello_from_rust is now compiled into PHP.
Segmentation fault: 11

还不易,php 已步向我们的 c 扩展,见到大家的采纳措施列表并且调用。接着,c 扩大已跻身大家的 rust 库,开始打字与印刷大家的字符串。那很有意思!但是……这段错误的后果产生了何等?

 

正如本身所涉及的,这里是选取了 Rust 相关的 println! 宏,不过自个儿从没对它做越来越调和。倘诺我们从大家的 Rust 库中删除并赶回三个 char* 替代,段错误就能够收敛。

这里是 Rust 的代码:

#![crate_type = "staticlib"]

#![feature(libc)]
extern crate libc;
use std::ffi::{CStr, CString};

#[no_mangle]
pub extern "C" fn hello_from_rust(name: *const libc::c_char) -> *const libc::c_char {
    let buf_name = unsafe { CStr::from_ptr(name).to_bytes() };
    let str_name = String::from_utf8(buf_name.to_vec()).unwrap();
    let c_name   = format!("Hello from Rust, {}", str_name);

    CString::new(c_name).unwrap().as_ptr()
}

并变更 C 头文件:

#ifndef __HELLO
#define __HELLO

const char * hello_from_rust(const char *name);

#endif

还要更换 C 扩大文件:

PHP_FUNCTION(confirm_hello_from_rust_compiled)
{
    char *arg = NULL;
    int arg_len, len;
    char *strg;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
        return;
    }

    char *str;
    str = hello_from_rust("Jared (from PHP!!)!");
    printf("%s/n", str);

    len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "hello_from_rust", arg);
    RETURN_STRINGL(strg, len, 0);
}

编写翻译它会获得.a的二个文件,libhello_from_rust.a。那是多个静态的库,包括它本人有着的依据关系,而且我们在编写翻译三个C程序的时候链接它,那让大家能做持续的事体。注意:在大家编写翻译后会得到如下输出:  

不行的微基准

那么为啥您还要如此做?小编还确确实实未有在切切实实世界里应用过这么些。但是作者实在以为斐波那契种类算法就是一个好的事例来验证贰个PHP拓宽怎么着很基本。平日是当机立断(在Ruby中):

def fib(at) do
    if (at == 1 || at == 0)
        return at
    else
        return fib(at - 1) + fib(at - 2)
    end
end

还要能够由此不选用递回来修改那不佳的性格:

def fib(at) do
    if (at == 1 || at == 0)
        return at
    elsif (val = @cache[at]).present?
        return val  
    end

    total  = 1
    parent = 1
    gp     = 1

    (1..at).each do |i|
        total  = parent + gp
        gp     = parent
        parent = total
    end

    return total
end

那正是说大家围绕它来写四个例子,一个在PHP中,一个在Rust中。看看哪些越来越快。上面是PHP版:

def fib(at) do
    if (at == 1 || at == 0)
        return at
    elsif (val = @cache[at]).present?
        return val  
    end

    total  = 1
    parent = 1
    gp     = 1

    (1..at).each do |i|
        total  = parent + gp
        gp     = parent
        parent = total
    end

    return total
end

那是它的运维结果:

$ time php php_fib.php

real    0m2.046s
user    0m1.823s
sys 0m0.207s

现行反革命我们来做Rust版。上边是库财富:

#![crate_type = "staticlib"]

fn fib(at: usize) -> usize {
    if at == 0 {
        return 0;
    } else if at == 1 {
        return 1;
    }

    let mut total  = 1;
    let mut parent = 1;
    let mut gp     = 0;
    for _ in 1 .. at {
        total  = parent + gp;
        gp     = parent;
        parent = total;
    }

    return total;
}

#[no_mangle]
pub extern "C" fn rust_fib(at: usize) -> usize {
    fib(at)
}

瞩目,小编编写翻译的库rustc – O rust_lib.rs使编译器优化(因为我们是此处的科班)。这里是C扩展源(相关摘录):

PHP_FUNCTION(confirm_rust_fib_compiled)
{
    long number;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &number) == FAILURE) {
        return;
    }

    RETURN_LONG(rust_fib(number));
}

运行PHP脚本:

<?php
$br = (php_sapi_name() == "cli")? "":"<br>";

if(!extension_loaded('rust_fib')) {
    dl('rust_fib.' . PHP_SHLIB_SUFFIX);
}

for ($i = 0; $i < 100000; $i ++) {
    confirm_rust_fib_compiled(92);
}
?>

那正是它的运行结果:

$ time php rust_fib.php

real    0m0.586s
user    0m0.342s
sys 0m0.221s

你可以瞥见它比前面二个快了三倍!完美的Rust微基准!

note: link against the following native artifacts when linking against this static library
note: the order and any duplication can be significant on some platforms, and so may need to be preserved
note: library: Systemnote: library: pthread
note: library: c
note: library: m

总结

此处大概没有得出什么结论。小编不明确在Rust上写一个PHP的扩张是二个好的主见,可是花费一些时间去斟酌Rust,PHP和C,那是三个很好的格局。

万一您愿意查看全数代码或然查看修改记录,能够访谈GitHub Repo。

那正是Rust编写翻译器在我们不接纳那么些依附的时候所告诉大家须求链接什么。

从C中调用Rust

既然如此大家有了一个库,一定要做两件事来有限支撑它从C中可调用。首先,我们必要为它成立三个C的头文件,hello_from_rust.h。然后在我们编写翻译的时候链接到它。

上面是头文件:  

// hello_from_rust.h
#ifndef __HELLO
#define __HELLO

void hello_from_rust(const char *name);

#endif

那是三个一定底子的头文件,仅仅为了三个简洁明了的函数提供签字/定义。接着我们供给写多个C程序并利用它。  

// hello.c
#include 
#include 
#include "hello_from_rust.h"

int main(int argc, char *argv[]) {
 hello_from_rust("Jared!");
}

咱们通过运营一下代码来编写翻译它:  

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告诉gcc不要链接那个“本地的古董”,为了当编写翻译大家的Rust库时Rust编写翻译器能够提供出来。

经运维上面的代码大家能够收获四个二进制的公文:  

$ ./hello_c
Hello from Rust, Jared!

精粹!大家刚刚从C中调用了Rust库。今后大家供给知道Rust库是怎么进入三个PHP扩大的。

从 php 中调用 c

该片段花了本身有个别时光来弄明白,在此个世界上,该文书档案在 php 扩充中并非最棒的。最棒的有的是出自绑定叁个本子 ext_skel 的 php 源(大大多意味着“增添骨架”)即生成大大多你必要的样本代码。  你能够经过下载来早先,和未分配的定额的 php 源,把代码写进 php 目录而且运行:

 $ cd ext/
$ ./ext_skel --extname=hello_from_rust

  那将转移需求创立 php 扩大的主干骨架。以往,移动你随处想有个别地涵养您的扩展的文书夹。并且一举手一投足你的

  •     .rust 源
  •     .rust库
  •     .c header

进去同三个索引。由此,现在您应有看看像那样的二个目录:

 .
├── CREDITS
├── EXPERIMENTAL
├── 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
└── tests
 └── 001.phpt

贰个目录,10个文件

您能够在 php docs 在上边看见关于这么些文件很好的叙说。创立多少个扩充的文本。大家将透过编写制定config.m4 来开始吧。

不表达,上面便是自身的硕果:  

PHP_ARG_WITH(hello_from_rust, for hello_from_rust support,
[ --with-hello_from_rust    Include hello_from_rust support])

if test "$PHP_HELLO_FROM_RUST" != "no"; then
 PHP_SUBST(HELLO_FROM_RUST_SHARED_LIBADD)

 PHP_ADD_LIBRARY_WITH_PATH(hello_from_rust, ., HELLO_FROM_RUST_SHARED_LIBADD)

 PHP_NEW_EXTENSION(hello_from_rust, hello_from_rust.c, $ext_shared)
fi

正如笔者所知晓的那样,那几个是基本的宏命令。不过至于这么些宏命令的文书档案是一对一不佳的(举个例子:google"PHP_ADD_LIBRARY_WITH_PATH"并未现身PHP团队所写的结果)。小编不常那几个PHP_ADD_LIBRARY_PATH宏命令在几个人所研究的在八个PHP拓宽里链接多少个静态库的先前的线程里。在批评中其余的引荐应用的宏命令是在本身运维ext_skel后产生的。

上一篇:没有了 下一篇:没有了