Heim  >  Artikel  >  Backend-Entwicklung  >  PHP-Erweiterung für Rust

PHP-Erweiterung für Rust

*文
*文Original
2017-12-26 16:54:582200Durchsuche

In diesem Artikel wird hauptsächlich die Verwendung von Rust-Erweiterungen in PHP-Programmen vorgestellt. Rust ist eine neu aufkommende kompilierte Sprache mit hervorragender Leistung. Ich hoffe, es hilft allen.

Rust in C oder PHP

Mein grundlegender Ausgangspunkt besteht darin, kompilierbaren Rust-Code in eine Bibliothek zu schreiben und einige C-Header-Dateien dafür zu schreiben und eine Erweiterung darin zu erstellen C für das aufgerufene PHP. Es ist nicht einfach, aber es macht Spaß.
Rust FFI (Foreign Function Interface)

Das erste, was ich tat, war, mit Rusts Foreign Function Interface herumzuspielen, das Rust mit C verbindet. Ich habe einmal eine flexible Bibliothek mit einer einfachen Methode (hello_from_rust) mit einer einzigen Deklaration (einem Zeiger auf ein C-Zeichen, auch als String bezeichnet) geschrieben. Hier ist die Ausgabe von „Hello from Rust“ nach der Eingabe.

// 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);
}

Ich habe es aus einer Rust-Bibliothek aufgeteilt, die von C (oder was auch immer!) aufgerufen wird. Hier ist eine gute Erklärung, was als nächstes kommt.

Beim Kompilieren erhält man eine Datei mit der Datei .a, libhello_from_rust.a. Dies ist eine statische Bibliothek, die alle ihre eigenen Abhängigkeiten enthält. Wir verknüpfen sie beim Kompilieren eines C-Programms, sodass wir nachfolgende Aufgaben ausführen können. Hinweis: Nach dem Kompilieren erhalten wir die folgende Ausgabe:

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

Dies ist der Rust-Compiler, wenn wir dies nicht tun Verwenden Sie diese Abhängigkeit. Die Zeit sagt uns, was verknüpft werden muss.

Rust von C aus aufrufen

Da wir nun eine Bibliothek haben, müssen wir zwei Dinge tun, um sicherzustellen, dass sie von C aus aufrufbar ist. Zuerst müssen wir eine C-Header-Datei dafür erstellen, hello_from_rust.h. Dann verlinken Sie darauf, wenn wir es kompilieren.

Hier sind die Header-Dateien:

// hello_from_rust.h
#ifndef __HELLO
#define __HELLO
 
void hello_from_rust(const char *name);
 
#endif

Dies ist eine ziemlich einfache Header-Datei, nur für einen Einfache Funktionen stellen Signaturen/Definitionen bereit. Als nächstes müssen wir ein C-Programm schreiben und es verwenden.

// hello.c
#include <stdio.h>
#include <stdlib.h>
#include "hello_from_rust.h"
 
int main(int argc, char *argv[]) {
 hello_from_rust("Jared!");
}

Kompilieren wir es, indem wir diesen Code ausführen:

gcc -Wall -o hello_c hello.c -L /Users/jmcfarland/code/rust/php-hello-rust -lhello_from_rust -lSystem -lpthread -lc -lm

Beachten Sie, dass -lSystem -lpthread -lc -lm am Ende gcc anweist, diese „lokalen Antiquitäten“ nicht zu verknüpfen, damit der Rust-Compiler sie beim Kompilieren unserer Rust-Bibliothek bereitstellen kann.

Indem wir den folgenden Code ausführen, können wir eine Binärdatei erhalten:

$ ./hello_c
Hello from Rust, Jared!

Wunderschön! Wir haben gerade die Rust-Bibliothek von C aus aufgerufen. Jetzt müssen wir verstehen, wie eine Rust-Bibliothek in eine PHP-Erweiterung gelangt.


C von PHP aus aufrufen

Es hat einige Zeit gedauert, bis ich herausgefunden habe, dass die Dokumentation in dieser Welt nicht in der PHP-Erweiterung enthalten ist. Das Beste. Das Beste daran ist, dass die PHP-Quelle aus der Bündelung eines Skripts ext_skel (steht meist für „Extended Skeleton“) stammt, das den größten Teil des benötigten Boilerplate-Codes generiert. Sie können beginnen, indem Sie die nicht zitierte PHP-Quelle herunterladen, den Code in das PHP-Verzeichnis schreiben und Folgendes ausführen:

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

Dadurch wird der benötigte Code generiert um ein erweitertes PHP-Grundgerüst zu erstellen. Verschieben Sie nun die Ordner überall dort, wo Sie Ihre Erweiterungen lokal behalten möchten. Und verschieben Sie Ihre

  • .rust-Quelle

  • .rust-Bibliothek

  • .c-Header

Geben Sie dasselbe Verzeichnis ein. Nun sollten Sie sich also ein Verzeichnis wie dieses ansehen:

 .
├── 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

Ein Verzeichnis, 11 Dateien

Sie Eine gute Beschreibung dieser Dateien finden Sie in den PHP-Dokumenten oben. Erstellen Sie eine erweiterte Datei. Wir beginnen mit der Bearbeitung von config.m4.

Ohne Erklärung hier meine Ergebnisse:

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

So wie ich es verstehe, handelt es sich um eine grundlegender Makrobefehl. Die Dokumentation zu diesen Makros ist jedoch recht dürftig (Beispiel: Google „PHP_ADD_LIBRARY_WITH_PATH“ zeigt keine vom PHP-Team geschriebenen Ergebnisse an). Ich bin in einem früheren Thread auf dieses PHP_ADD_LIBRARY_PATH-Makro gestoßen, in dem jemand über die Verknüpfung einer statischen Bibliothek in einer PHP-Erweiterung sprach. Die anderen empfohlenen Makros in den Kommentaren wurden generiert, nachdem ich ext_skel ausgeführt habe.

Da wir nun die Konfiguration eingerichtet haben, müssen wir die Bibliothek tatsächlich über das PHP-Skript aufrufen. Dazu müssen wir die automatisch generierte Datei hello_from_rust.c ändern. Zuerst fügen wir die Header-Datei hello_from_rust.h zum Befehl include hinzu. Dann müssen wir die Definitionsmethode von secure_hello_from_rust_compiled ändern.

#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);
}

Hinweis: Ich habe hello_from_rust("Jared (fromPHP!!)!"); hinzugefügt.


Jetzt können wir versuchen, unsere Erweiterung zu erstellen:

$ phpize
$ ./configure
$ sudo make install

Das ist es, generieren Sie unsere Metakonfiguration, führen Sie die aus generierten Konfigurationsbefehl und installieren Sie die Erweiterung. Bei der Installation musste ich selbst sudo verwenden, da mein Benutzer nicht die PHP-Erweiterung für das Installationsverzeichnis besaß.

Jetzt können wir es ausführen!

$ 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);
}

无用的微基准

那么为什么你还要这样做?我还真的没有在现实世界里使用过这个。但是我真的认为斐波那契序列算法就是一个好的例子来说明一个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(&#39;rust_fib&#39;)) {
 dl(&#39;rust_fib.&#39; . 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微基准!

相关推荐:

macOS 中使用 phpize 动态添加 PHP 扩展的错误解决方法

Linux 下 PHP 扩展 cURL 编译安装

PHP 扩展库

Das obige ist der detaillierte Inhalt vonPHP-Erweiterung für Rust. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Vorheriger Artikel:PHP-Socket-UDP-KommunikationNächster Artikel:PHP-Socket-UDP-Kommunikation