Maison  >  Article  >  développement back-end  >  Extension PHP à Rust

Extension PHP à Rust

*文
*文original
2017-12-26 16:54:582154parcourir

Cet article présente principalement comment utiliser les extensions Rust dans les programmes PHP. Rust est un langage compilé récemment apparu avec des performances exceptionnelles. J'espère que cela aide tout le monde.

Rust en C ou PHP

Mon point de départ de base est d'écrire du code Rust compilable dans une bibliothèque et d'écrire des fichiers d'en-tête C pour celui-ci, de créer une extension dans C pour le PHP appelé. Ce n'est pas facile, mais c'est amusant.
Rust FFI (interface de fonction étrangère)

La première chose que j'ai faite a été de jouer avec l'interface de fonction étrangère de Rust connectant Rust à C. Une fois, j'ai écrit une bibliothèque flexible en utilisant une méthode simple (hello_from_rust) avec une seule déclaration (un pointeur vers un caractère C, autrement appelé chaîne). Voici la sortie de "Bonjour de Rust" après la saisie.

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

Je l'ai séparé d'une bibliothèque Rust appelée depuis C (ou autre !). Voici une bonne explication de ce qui vient ensuite.

Le compiler obtiendra un fichier de .a, libhello_from_rust.a. Il s'agit d'une bibliothèque statique qui contient toutes ses propres dépendances, et nous la lions lors de la compilation d'un programme C, ce qui nous permet de faire des choses ultérieures. Remarque : Après avoir compilé, nous obtiendrons le résultat suivant :

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

Il s'agit du compilateur Rust alors que nous ne le faisons pas. utilisez cette dépendance. Le temps nous indique ce qui doit être lié.

Appeler Rust depuis C

Maintenant que nous avons une bibliothèque, nous devons faire deux choses pour la rendre appelable depuis C. Tout d’abord, nous devons créer un fichier d’en-tête C pour cela, hello_from_rust.h. Ensuite, créez un lien vers celui-ci lorsque nous compilerons.

Voici les fichiers d'en-tête :

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

Il s'agit d'un fichier d'en-tête assez basique, juste pour un Des fonctions simples fournissent des signatures/définitions. Ensuite, nous devons écrire un programme C et l'utiliser.

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

Compilons-le en exécutant ce code :

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

Notez que le -lSystem -lpthread -lc -lm à la fin indique à gcc de ne pas lier ces "antiquités locales" afin que le compilateur Rust puisse les fournir lors de la compilation de notre bibliothèque Rust.

En exécutant le code suivant, nous pouvons obtenir un fichier binaire :

$ ./hello_c
Hello from Rust, Jared!

Magnifique ! Nous venons d'appeler la bibliothèque Rust depuis C. Nous devons maintenant comprendre comment une bibliothèque Rust entre dans une extension PHP.


Appelez c depuis php

Cette partie m'a pris un certain temps à comprendre, dans ce monde, la documentation n'est pas dans l'extension php Le meilleur. La meilleure partie est que la source php provient du regroupement d'un script ext_skel (signifie principalement "Extended Skeleton") qui génère la plupart du code passe-partout dont vous avez besoin. Vous pouvez commencer par télécharger la source php non citée, écrire le code dans le répertoire php et exécuter :

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

Cela générera le code nécessaire pour créer un squelette de base étendu php. Maintenant, déplacez les dossiers partout où vous souhaitez conserver vos extensions localement. Et déplacez votre

  • source .rust

  • bibliothèque .rust

  • en-tête .c

Entrez dans le même répertoire. Alors maintenant, vous devriez regarder un répertoire comme celui-ci :

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

Un répertoire, 11 fichiers

Vous Vous pouvez voir une bonne description de ces fichiers dans la documentation php ci-dessus. Créez un fichier étendu. Nous allons commencer par éditer config.m4.

Sans explication, voici mes résultats :

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

Si je comprends bien, ces C'est un commande macro de base. Mais la documentation sur ces macros est assez pauvre (par exemple : google "PHP_ADD_LIBRARY_WITH_PATH" n'affiche pas les résultats écrits par l'équipe PHP). Je suis tombé sur cette macro PHP_ADD_LIBRARY_PATH dans un fil de discussion précédent où quelqu'un parlait de lier une bibliothèque statique dans une extension PHP. Les autres macros recommandées dans les commentaires ont été générées après avoir exécuté ext_skel.

Maintenant que nous avons la configuration, nous devons réellement appeler la bibliothèque à partir du script PHP. Pour ce faire, nous devons modifier le fichier généré automatiquement, hello_from_rust.c. Nous ajoutons d’abord le fichier d’en-tête hello_from_rust.h à la commande include. Ensuite, nous devons modifier la méthode de définition de 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);
}

Remarque : j'ai ajouté hello_from_rust("Jared (fromPHP !!)!");.


Maintenant, nous pouvons essayer de construire notre extension :

$ phpize
$ ./configure
$ sudo make install

Ça y est, générez notre méta configuration, lancez le commande de configuration générée et installez l’extension. Lors de l'installation, j'ai dû utiliser sudo moi-même car mon utilisateur ne possédait pas l'extension php pour le répertoire d'installation.

Maintenant, nous pouvons l'exécuter !

$ 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 扩展库

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Article précédent:Communication PHP-Socket UDPArticle suivant:Communication PHP-Socket UDP