Use the Rust Extension Method in the PHP program, and the php program rust Extension

Source: Internet
Author: User
Tags ffi

Use the Rust Extension Method in the PHP program, and the php program rust Extension

Rust in C or PHP

My basic starting point is to write some compiled Rust code into a library, write some C header files for it, and make an extension for the called PHP in C. Although not very simple, it is very interesting.
Rust FFI (foreign function interface)

The first thing I do is to play with the external function interfaces of Rust connected to C. I used a simple method (hello_from_rust) to write a flexible library with a single declaration (a pointer to a C char, otherwise known as a string ), the following is the "Hello from Rust" output after the input ".
 

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

I'm from C (or other !) The Rust library called in split it. The following is a good explanation.

Compile it to get a. a file, libhello_from_rust.a. This is a static library that contains all its dependencies, And we link it when compiling a C program, which allows us to do the following. Note: After compilation, we will get the following output:
 

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

This is what the Rust compiler tells us when we don't use this dependency.

Call Rust from C

Since we have a library, we have to do two things to ensure that it can be called from C. First, we need to create a C header file for it, hello_from_rust.h. Then link to it during compilation.

The following is the header file:
 

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

This is a fairly basic header file. It only provides signatures/definitions for a simple function. Then we need to write a C program and use it.
 

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

Run the code to compile it:
 

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

Note that the-lSystem-lpthread-lc-lm at the end tells gcc not to link those "local antiques" to provide the Rust compiler when compiling our Rust library.

Run the following code to obtain a binary file:
 

$ ./hello_cHello from Rust, Jared!

Pretty! We just called the Rust library from C. Now we need to understand how the Rust library enters a PHP extension.


Call c from php

It took me some time to understand that in this world, this document is not the best in php extensions. The best part is that php sources (most of which represent the "Extension skeleton") bound to a script ext_skel generate most of the sample code you need. You can start by downloading and write the code into the php Directory and run the following code:

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

This will generate the basic skeleton for creating php extensions. Now, move the folder that you want to partially keep your extension. And move your

  • . Rust Source
  • . Rust Library
  • . C header

Enter the same directory. So now you should look at a directory like this:

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

One directory and 11 files

You can see a good description of these files in php docs. Create an extended file. We will start by editing config. m4.

Without explanation, the following are my achievements:
 

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

As I understand, these are basic macro commands. However, the documentation on these macro commands is rather bad (for example, google "PHP_ADD_LIBRARY_WITH_PATH" does not show the results written by the PHP team ). I happen to have this PHP_ADD_LIBRARY_PATH macro command in some people talk about connecting to the previous thread of a static library in a PHP extension. Other recommended macro commands in the comments are generated after I run ext_skel.

Since we have made configuration settings, we need to actually call the library from the PHP script. For this reason, we need to modify the automatically generated file hello_from_rust.c. First, add the header file hello_from_rust.h to the include command. Then we need to modify the definition method of 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);}

Note: I added hello_from_rust ("Jared (fromPHP !!)! ");.


Now, we can try to build our extension:

$ phpize$ ./configure$ sudo make install

It generates our meta configuration, runs the generated configuration command, and then installs the extension. During installation, I must use sudo myself because my users do not have php extensions for the installation directory.

Now we can run it!

$ php hello_from_rust.phpFunctions available in the test extension:confirm_hello_from_rust_compiledHello 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

Pretty good. php has entered our c extension. We can see our application method list and call it. Then, the c extension has entered our rust library and started printing our strings. That's interesting! But what happened to the wrong ending?

As I mentioned, the Rust-related println is used here! Macro, but I did not perform further debugging on it. If we delete it from our Rust library and return a char * replacement, the segment error will disappear.

Here is the code of Rust:
 Copy codeThe Code is as follows:
#! [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 ()
}

And change the C header file:
 

#ifndef __HELLO#define __HELLO const char * hello_from_rust(const char *name); #endif

You also need to change the C extension file:
 

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

Useless micro-Benchmark

So why are you still doing this? I have never used this in the real world. But I really think the Fibonacci sequence algorithm is a good example to illustrate how PHP expansion works. Usually straightforward (in Ruby ):
 

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

In addition, this performance can be improved without recursion:
 

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 totalend

Let's write two examples around it, one in PHP and the other in Rust. See which one is faster. Below is the PHP version:
 

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 totalend

Here is the running result:
 

$ time php php_fib.php real 0m2.046suser 0m1.823ssys 0m0.207s

Now let's make the Rust version. Below are the library resources:
 Copy codeThe Code is as follows:
#! [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 .. {
Total = parent + gp;
Gp = parent;
Parent = total;
}
 
Return total;
}
 
# [No_mangle]
Pub extern "C" fn rust_fib (at: usize)-> usize {
Fib ()
}

Note that the compiled library rustc-O rust_lib.rs enables Compiler Optimization (because we are the standard here ). Here is the C extension source (related excerpt ):
 

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

Run the PHP script:
 

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

This is the running result:

$ time php rust_fib.php real 0m0.586suser 0m0.342ssys 0m0.221s

You can see that it is three times faster than the former! Perfect Rust micro benchmark!

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.