├── .gitignore ├── makefile ├── r2p-fib ├── Cargo.toml └── src │ └── lib.rs ├── test-fib.py ├── ext └── rust │ ├── php_rust.h │ ├── .gitignore │ └── rust.c ├── test-ffi.php ├── test-ffi2.php ├── docs └── php-ext.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist 3 | php-src 4 | r2p-fib/target 5 | r2p-fib/Cargo.lock 6 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | 2 | # 测试fib函数 3 | test-php: 4 | ./dist/bin/php test-ffi.php 5 | 6 | # 测试python下fib 7 | test-python: 8 | python test-fib.py 9 | 10 | # 测试字符串调用 11 | test-php2: 12 | ./dist/bin/php test-ffi2.php -------------------------------------------------------------------------------- /r2p-fib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "r2p-fib" 3 | version = "0.1.0" 4 | authors = ["llgoer "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | libc = "*" 9 | 10 | [lib] 11 | name = "r2pfib" 12 | crate-type = ["dylib"] -------------------------------------------------------------------------------- /test-fib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | import time 4 | 5 | def fib(n): 6 | if n <= 2: 7 | return 1 8 | else: 9 | return fib(n - 1) + fib(n - 2) 10 | 11 | start_time = time.time() 12 | for x in xrange(1,1000000): 13 | v = fib(12) 14 | print("[Python]执行时间:%s" % (time.time() - start_time)) 15 | 16 | # [Python]执行时间:56.9069800377 -------------------------------------------------------------------------------- /ext/rust/php_rust.h: -------------------------------------------------------------------------------- 1 | /* rust extension for PHP */ 2 | 3 | #ifndef PHP_RUST_H 4 | # define PHP_RUST_H 5 | 6 | extern zend_module_entry rust_module_entry; 7 | # define phpext_rust_ptr &rust_module_entry 8 | 9 | # define PHP_RUST_VERSION "0.1.0" 10 | 11 | # if defined(ZTS) && defined(COMPILE_DL_RUST) 12 | ZEND_TSRMLS_CACHE_EXTERN() 13 | # endif 14 | 15 | #endif /* PHP_RUST_H */ 16 | 17 | // 来自rust的fib函数 18 | int32_t fib(int32_t n); -------------------------------------------------------------------------------- /ext/rust/.gitignore: -------------------------------------------------------------------------------- 1 | *.lo 2 | *.la 3 | .libs 4 | acinclude.m4 5 | aclocal.m4 6 | autom4te.cache 7 | build 8 | config.guess 9 | config.h 10 | config.h.in 11 | config.log 12 | config.nice 13 | config.status 14 | config.sub 15 | configure 16 | configure.ac 17 | configure.in 18 | include 19 | install-sh 20 | libtool 21 | ltmain.sh 22 | Makefile 23 | Makefile.fragments 24 | Makefile.global 25 | Makefile.objects 26 | missing 27 | mkinstalldirs 28 | modules 29 | run-tests.php 30 | tests/*/*.diff 31 | tests/*/*.out 32 | tests/*/*.php 33 | tests/*/*.exp 34 | tests/*/*.log 35 | tests/*/*.sh 36 | -------------------------------------------------------------------------------- /test-ffi.php: -------------------------------------------------------------------------------- 1 | fib(12); 30 | } 31 | 32 | echo '[Rust]Debug执行时间:' . (microtime(true) - $time_start).PHP_EOL; 33 | 34 | // release模式 35 | $ffiRelease = FFI::cdef( 36 | "int32_t fib(int32_t n);", 37 | "r2p-fib/target/release/libr2pfib.$libExtension"); 38 | 39 | $time_start = microtime(true); 40 | for ($i=0; $i < 10000000; $i++) { 41 | $v = $ffiRelease->fib(12); 42 | } 43 | 44 | echo '[Rust]Release执行时间:' . (microtime(true) - $time_start).PHP_EOL; 45 | 46 | // 这里我们采用Rust编写的一个PHP扩展 47 | $time_start = microtime(true); 48 | for ($i=0; $i < 10000000; $i++) { 49 | $v = rust_fib(12); 50 | } 51 | 52 | echo '[Rust]Ext执行时间:' . (microtime(true) - $time_start).PHP_EOL; 53 | 54 | 55 | // 这里我们采用C编写的一个PHP扩展 56 | $time_start = microtime(true); 57 | for ($i=0; $i < 10000000; $i++) { 58 | $v = c_fib(12); 59 | } 60 | 61 | echo '[C]Ext执行时间:' . (microtime(true) - $time_start).PHP_EOL; 62 | 63 | ?> -------------------------------------------------------------------------------- /r2p-fib/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate test; 3 | extern crate libc; 4 | 5 | use libc::c_char; 6 | use std::ffi::CString; 7 | use std::iter; 8 | 9 | #[no_mangle] 10 | pub extern fn fib(n: i32) -> i32 { 11 | return match n { 12 | 1 | 2 => 1, 13 | n => fib(n - 1) + fib(n - 2) 14 | } 15 | } 16 | 17 | #[no_mangle] 18 | pub extern fn text_generate(length: u8) -> *mut c_char { 19 | let mut song = String::from("💣"); 20 | song.extend(iter::repeat("na ").take(length as usize)); 21 | song.push_str("Batman! 💣"); 22 | 23 | let c_str_song = CString::new(song).unwrap(); 24 | c_str_song.into_raw() 25 | } 26 | 27 | #[no_mangle] 28 | pub extern fn text_free(s: *mut c_char) { 29 | unsafe { 30 | if s.is_null() {return} 31 | CString::from_raw(s) 32 | }; 33 | } 34 | 35 | // rust生成 36 | fn rust_text_generate(length: u8) -> String { 37 | let mut song = String::from("💣"); 38 | song.extend(iter::repeat("na ").take(length as usize)); 39 | song.push_str("Batman! 💣"); 40 | 41 | song 42 | } 43 | 44 | // cargo +nightly bench 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | use test::Bencher; 49 | 50 | #[test] 51 | fn test_rust_text_generate() { 52 | assert_eq!(rust_text_generate(12), "💣na na na na na na na na na na na na Batman! 💣"); 53 | } 54 | 55 | #[bench] 56 | fn bench_text_generate(b: &mut Bencher) { 57 | b.iter(|| { 58 | let result = text_generate(12); 59 | text_free(result); 60 | }); 61 | } 62 | 63 | #[bench] 64 | fn bench_rust_text_generate(b: &mut Bencher) { 65 | b.iter(|| { 66 | let _ = rust_text_generate(12); 67 | }); 68 | } 69 | } -------------------------------------------------------------------------------- /test-ffi2.php: -------------------------------------------------------------------------------- 1 | text_generate(12); // 这里生成文本 23 | $result = FFI::string($rstr); // 转成php字符串 24 | $ffiDebug->text_free($rstr); // 释放掉内存 25 | } 26 | // debug在8300ns一次 27 | echo '[Rust]debug执行时间:' . (microtime(true) - $time_start).PHP_EOL; 28 | var_dump($result); 29 | 30 | $ffiRelease = FFI::cdef( 31 | "char * text_generate(int8_t n); 32 | void text_free(char *s);", 33 | "r2p-fib/target/release/libr2pfib.$libExtension"); 34 | 35 | // 将返回的char*转换为php的字符串 36 | $time_start = microtime(true); 37 | for ($i=0; $i < 1000000; $i++) { 38 | $rstr = $ffiRelease->text_generate(12); // 这里生成文本 39 | $result = FFI::string($rstr); // 转成php字符串 40 | $ffiRelease->text_free($rstr); // 释放掉内存 41 | } 42 | // release基本上在1600ns一次 43 | // 测试在rust中每次操作在700ns一次,那就是这里的FFI转值消耗了将近1000ns 44 | echo '[Rust]release执行时间:' . (microtime(true) - $time_start).PHP_EOL; 45 | var_dump($result); 46 | 47 | // 这里能够正常打印 48 | // $ffi->printf($ffiDebug->text_generate(12)); 49 | 50 | function text_generate($num) { 51 | $result = "💣"; 52 | $result .= str_repeat("na ",$num); 53 | $result .= "Batman! 💣"; 54 | return $result; 55 | } 56 | 57 | $time_start = microtime(true); 58 | for ($i=0; $i < 1000000; $i++) { 59 | $result = ""; 60 | $result = text_generate(12); 61 | } 62 | // 这里计算出PHP这样一个生成字符串的操作,实际是需要270ns左右 63 | echo '[PHP]执行时间:' . (microtime(true) - $time_start).PHP_EOL; 64 | var_dump($result); 65 | ?> -------------------------------------------------------------------------------- /docs/php-ext.md: -------------------------------------------------------------------------------- 1 | # 创建一个PHP扩展调用Rust动态库 2 | 3 | 这里我们尝试编写一个PHP扩展,调用Rust编写的动态库。 4 | 5 | ## 生成PHP扩展目录结构 6 | 7 | 我们先使用PHP的`ext_skel`来生成一个PHP扩展源码结构。 8 | 9 | 执行 10 | ``` 11 | $ ./dist/bin/php ./php-src/ext/ext_skel.php --ext rust 12 | Copying config scripts... done 13 | Copying sources... done 14 | Copying tests... done 15 | 16 | Success. The extension is now ready to be compiled. To do so, use the 17 | following steps: 18 | 19 | cd /path/to/php-src/rust 20 | phpize 21 | ./configure 22 | make 23 | 24 | Don't forget to run tests once the compilation is done: 25 | make test 26 | 27 | Thank you for using PHP! 28 | ``` 29 | 30 | 我们发现在php-src的ext目录下已经生成了一个叫`rust`的目录 31 | 32 | ## 编译安装扩展 33 | 34 | 进入扩展目录,然后执行 35 | 36 | ``` 37 | ./phpize 38 | ./configure 39 | make install 40 | ``` 41 | 42 | 编译完成后,修改php.ini配置,添加 43 | 44 | ``` 45 | extension=rust 46 | ``` 47 | 48 | 然后再执行,查看是否成功安装刚才编译的模块: 49 | 50 | ``` 51 | $ ./php -m 52 | [PHP Modules] 53 | Core 54 | ctype 55 | date 56 | dom 57 | FFI 58 | fileinfo 59 | filter 60 | hash 61 | iconv 62 | json 63 | libxml 64 | pcre 65 | PDO 66 | pdo_sqlite 67 | Phar 68 | posix 69 | Reflection 70 | rust 71 | session 72 | SimpleXML 73 | SPL 74 | sqlite3 75 | standard 76 | tokenizer 77 | xml 78 | xmlreader 79 | xmlwriter 80 | 81 | [Zend Modules] 82 | ``` 83 | 我们发现多了`rust`,表示模块能够被正确安装及加载。 84 | 85 | ## Rust开发一个PHP扩展 86 | 87 | 首先我们从我们的`r2p-fib`中倒入我们用Rust编写的fib函数。 88 | 89 | 在`php_rust.h`中定义 90 | 91 | ``` 92 | // 来自rust的fib函数 93 | int32_t fib(int32_t n); 94 | ``` 95 | 96 | 然后在`rust.c`中实现我们扩展的处理 97 | ``` 98 | // 这里处理参数 99 | ZEND_BEGIN_ARG_INFO(arginfo_rust_fib, 0) 100 | ZEND_ARG_INFO(0, number) 101 | ZEND_END_ARG_INFO() 102 | 103 | // 这里具体的实现 104 | /* {{{ int rust_fib( [ int $var ] ) 105 | */ 106 | PHP_FUNCTION(rust_fib) 107 | { 108 | zend_long number = 0; 109 | zend_long result = 0; 110 | ZEND_PARSE_PARAMETERS_START(0, 1) 111 | Z_PARAM_OPTIONAL 112 | Z_PARAM_LONG(number) 113 | ZEND_PARSE_PARAMETERS_END(); 114 | 115 | if (number == 0) { 116 | RETURN_LONG(result); 117 | } else { 118 | result = fib(number); 119 | RETURN_LONG(result); 120 | } 121 | } 122 | /* }}}*/ 123 | 124 | ``` 125 | 这里我们只是贴出核心的代码部分。 126 | 127 | 为了更好的测试,我们同时编写了一个C的版本 128 | 129 | ``` 130 | // 这里直接编写一个C的fib调用 131 | int fibinC(int n) 132 | { 133 | if (n <= 1) 134 | return n; 135 | return fibinC(n - 1) + fibinC(n - 2); 136 | } 137 | /* {{{ int c_fib( [ int $var ] ) 138 | */ 139 | PHP_FUNCTION(c_fib) 140 | { 141 | zend_long number = 0; 142 | zend_long result = 0; 143 | ZEND_PARSE_PARAMETERS_START(0, 1) 144 | Z_PARAM_OPTIONAL 145 | Z_PARAM_LONG(number) 146 | ZEND_PARSE_PARAMETERS_END(); 147 | 148 | if (number == 0) { 149 | RETURN_LONG(result); 150 | } else { 151 | result = fibinC(number); 152 | RETURN_LONG(result); 153 | } 154 | } 155 | /* }}}*/ 156 | ZEND_BEGIN_ARG_INFO(arginfo_c_fib, 0) 157 | ZEND_ARG_INFO(0, number) 158 | ZEND_END_ARG_INFO() 159 | ``` 160 | 161 | ## 重新编译运行 162 | 163 | 这里编译需要增加对我们之前动态链接库的引用。 164 | 165 | 然后我们执行命令 166 | 167 | ``` 168 | $ ./php -r "echo rust_fib(12).PHP_EOL;" 169 | 144 170 | ``` 171 | 好了,144就是我们计算成功的结果。 172 | 173 | ## 性能测试 174 | 175 | 接下来我们扩充下我们之前的`test-ffi.php`,在其中加入: 176 | 177 | ``` 178 | // 这里我们采用Rust编写的一个PHP扩展 179 | $time_start = microtime(true); 180 | for ($i=0; $i < 10000000; $i++) { 181 | $v = rust_fib(12); 182 | } 183 | 184 | echo '[Rust]Ext执行时间:' . (microtime(true) - $time_start).PHP_EOL; 185 | ``` 186 | 187 | 执行`make test-php`,运行结果如下: 188 | 189 | ``` 190 | [PHP]fib执行时间:35.900850057602 191 | [Rust]Debug执行时间:22.036439180374 192 | [Rust]Release执行时间:6.8401432037354 193 | [Rust]Ext执行时间:5.19260597229 194 | [C]Ext执行时间:13.217513799667 195 | ``` 196 | 看来用Rust开发PHP的扩展性能上面是完全可以的。 197 | 198 | 完整的PHP扩展代码可以查看`ext/rust`目录。 -------------------------------------------------------------------------------- /ext/rust/rust.c: -------------------------------------------------------------------------------- 1 | /* rust extension for PHP */ 2 | 3 | #ifdef HAVE_CONFIG_H 4 | # include "config.h" 5 | #endif 6 | 7 | #include "php.h" 8 | #include "ext/standard/info.h" 9 | #include "php_rust.h" 10 | 11 | // 这里直接编写一个C的fib调用 12 | int fibinC(int n) 13 | { 14 | if (n <= 1) 15 | return n; 16 | return fibinC(n - 1) + fibinC(n - 2); 17 | } 18 | 19 | 20 | /* For compatibility with older PHP versions */ 21 | #ifndef ZEND_PARSE_PARAMETERS_NONE 22 | #define ZEND_PARSE_PARAMETERS_NONE() \ 23 | ZEND_PARSE_PARAMETERS_START(0, 0) \ 24 | ZEND_PARSE_PARAMETERS_END() 25 | #endif 26 | 27 | /* {{{ void rust_test1() 28 | */ 29 | PHP_FUNCTION(rust_test1) 30 | { 31 | ZEND_PARSE_PARAMETERS_NONE(); 32 | 33 | php_printf("The extension %s is loaded and working!\r\n", "rust"); 34 | } 35 | /* }}} */ 36 | 37 | /* {{{ string rust_test2( [ string $var ] ) 38 | */ 39 | PHP_FUNCTION(rust_test2) 40 | { 41 | char *var = "World"; 42 | size_t var_len = sizeof("World") - 1; 43 | zend_string *retval; 44 | 45 | ZEND_PARSE_PARAMETERS_START(0, 1) 46 | Z_PARAM_OPTIONAL 47 | Z_PARAM_STRING(var, var_len) 48 | ZEND_PARSE_PARAMETERS_END(); 49 | 50 | retval = strpprintf(0, "Hello %s", var); 51 | 52 | RETURN_STR(retval); 53 | } 54 | /* }}}*/ 55 | 56 | /* {{{ int rust_fib( [ int $var ] ) 57 | */ 58 | PHP_FUNCTION(rust_fib) 59 | { 60 | zend_long number = 0; 61 | zend_long result = 0; 62 | ZEND_PARSE_PARAMETERS_START(0, 1) 63 | Z_PARAM_OPTIONAL 64 | Z_PARAM_LONG(number) 65 | ZEND_PARSE_PARAMETERS_END(); 66 | 67 | if (number == 0) { 68 | RETURN_LONG(result); 69 | } else { 70 | result = fib(number); 71 | RETURN_LONG(result); 72 | } 73 | } 74 | /* }}}*/ 75 | 76 | /* {{{ int c_fib( [ int $var ] ) 77 | */ 78 | PHP_FUNCTION(c_fib) 79 | { 80 | zend_long number = 0; 81 | zend_long result = 0; 82 | ZEND_PARSE_PARAMETERS_START(0, 1) 83 | Z_PARAM_OPTIONAL 84 | Z_PARAM_LONG(number) 85 | ZEND_PARSE_PARAMETERS_END(); 86 | 87 | if (number == 0) { 88 | RETURN_LONG(result); 89 | } else { 90 | result = fibinC(number); 91 | RETURN_LONG(result); 92 | } 93 | } 94 | /* }}}*/ 95 | 96 | /* {{{ PHP_RINIT_FUNCTION 97 | */ 98 | PHP_RINIT_FUNCTION(rust) 99 | { 100 | #if defined(ZTS) && defined(COMPILE_DL_RUST) 101 | ZEND_TSRMLS_CACHE_UPDATE(); 102 | #endif 103 | 104 | return SUCCESS; 105 | } 106 | /* }}} */ 107 | 108 | /* {{{ PHP_MINFO_FUNCTION 109 | */ 110 | PHP_MINFO_FUNCTION(rust) 111 | { 112 | php_info_print_table_start(); 113 | php_info_print_table_header(2, "rust support", "enabled"); 114 | php_info_print_table_end(); 115 | } 116 | /* }}} */ 117 | 118 | /* {{{ arginfo 119 | */ 120 | ZEND_BEGIN_ARG_INFO(arginfo_rust_test1, 0) 121 | ZEND_END_ARG_INFO() 122 | 123 | ZEND_BEGIN_ARG_INFO(arginfo_rust_test2, 0) 124 | ZEND_ARG_INFO(0, str) 125 | ZEND_END_ARG_INFO() 126 | 127 | ZEND_BEGIN_ARG_INFO(arginfo_rust_fib, 0) 128 | ZEND_ARG_INFO(0, number) 129 | ZEND_END_ARG_INFO() 130 | 131 | ZEND_BEGIN_ARG_INFO(arginfo_c_fib, 0) 132 | ZEND_ARG_INFO(0, number) 133 | ZEND_END_ARG_INFO() 134 | /* }}} */ 135 | 136 | /* {{{ rust_functions[] 137 | */ 138 | static const zend_function_entry rust_functions[] = { 139 | PHP_FE(rust_test1, arginfo_rust_test1) 140 | PHP_FE(rust_test2, arginfo_rust_test2) 141 | PHP_FE(rust_fib, arginfo_rust_fib) 142 | PHP_FE(c_fib, arginfo_c_fib) 143 | PHP_FE_END 144 | }; 145 | /* }}} */ 146 | 147 | /* {{{ rust_module_entry 148 | */ 149 | zend_module_entry rust_module_entry = { 150 | STANDARD_MODULE_HEADER, 151 | "rust", /* Extension name */ 152 | rust_functions, /* zend_function_entry */ 153 | NULL, /* PHP_MINIT - Module initialization */ 154 | NULL, /* PHP_MSHUTDOWN - Module shutdown */ 155 | PHP_RINIT(rust), /* PHP_RINIT - Request initialization */ 156 | NULL, /* PHP_RSHUTDOWN - Request shutdown */ 157 | PHP_MINFO(rust), /* PHP_MINFO - Module info */ 158 | PHP_RUST_VERSION, /* Version */ 159 | STANDARD_MODULE_PROPERTIES 160 | }; 161 | /* }}} */ 162 | 163 | #ifdef COMPILE_DL_RUST 164 | # ifdef ZTS 165 | ZEND_TSRMLS_CACHE_DEFINE() 166 | # endif 167 | ZEND_GET_MODULE(rust) 168 | #endif 169 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-ffi-rust 2 | 3 | 本项目目标就是采用Rust编写扩展库,提供给PHP7.4的FFI扩展,看看是否能够抢救一下PHP。 4 | 5 | ## 拓展 6 | 7 | [创建一个PHP扩展调用Rust动态库](docs/php-ext.md) 8 | 9 | ## 测试环境 10 | 11 | macOS:10.13.6 12 | 13 | PHP:7.4.0beta1 14 | 15 | Rust:1.36.0 16 | 17 | ## 为macOS编译PHP 18 | 19 | 由于MacOS下没有找到合适的PHP7.4版本,所以只能自己动手编译。 20 | 21 | ### 下载 22 | 23 | 从PHP的[Github Release](https://github.com/php/php-src/releases)页面下载PHP7.4.0beta1版本。 24 | 25 | ### 编译PHP 26 | 27 | 确保系统安装`autoconf`,如果没有安装,执行`brew install autoconf`进行安装。 28 | 29 | 确保系统安装`bison`,`re2c`,`libffi`。 30 | 31 | ``` 32 | $ ./buildconf --force 33 | $ ./configure --prefix=/Users/llgoer/php-ffi-rust/dist --with-ffi 34 | $ make && make install 35 | ``` 36 | 37 | 这里注意,如果编译没有找到libffi,可以采用 38 | ``` 39 | FFI_CFLAGS=/xxx/libffi-3.2.1/include FFI_LIBS=/xxx/libffi/3.2.1/lib ./configure ... 40 | ``` 41 | 指定 42 | 43 | ### 运行PHP7.4 44 | 45 | 进入`dist`目录,我们可以看到我们已经编译好的PHP7.4。 46 | 47 | ```shell 48 | $ ./php -version 49 | PHP 7.4.0beta1 (cli) (built: Aug 2 2019 22:12:05) ( NTS ) 50 | Copyright (c) The PHP Group 51 | Zend Engine v3.4.0-dev, Copyright (c) Zend Technologies 52 | 53 | $ ./php -m 54 | [PHP Modules] 55 | Core 56 | ctype 57 | date 58 | dom 59 | FFI 60 | fileinfo 61 | filter 62 | hash 63 | iconv 64 | json 65 | libxml 66 | pcre 67 | PDO 68 | pdo_sqlite 69 | Phar 70 | posix 71 | Reflection 72 | session 73 | SimpleXML 74 | SPL 75 | sqlite3 76 | standard 77 | tokenizer 78 | xml 79 | xmlreader 80 | xmlwriter 81 | 82 | [Zend Modules] 83 | 84 | ``` 85 | 如果存在FFI扩展则表示编译正常。 86 | 87 | ## 安装Rust环境 88 | 89 | 这里就不多说了,可以去Rust官方网站看看。 90 | 91 | ## Rust编写一个动态连接库 92 | 93 | 执行 94 | 95 | ```shell 96 | $ cargo new r2p-fib --lib 97 | ``` 98 | 99 | 创建一个rust库,修改`Cargo.toml`,配置项目生成动态链接库 100 | 101 | ```toml 102 | [package] 103 | name = "r2p-fib" 104 | version = "0.1.0" 105 | authors = ["llgoer "] 106 | edition = "2018" 107 | 108 | [dependencies] 109 | name = "r2pfib" 110 | crate-type = ["dylib"] 111 | ``` 112 | 113 | 在lib.rs中创建一个导出的fib函数: 114 | 115 | ```rust 116 | #[no_mangle] 117 | pub extern fn fib(n: i32) -> i32 { 118 | return match n { 119 | 1 | 2 => 1, 120 | n => fib(n - 1) + fib(n - 2) 121 | } 122 | } 123 | ``` 124 | 125 | 执行`cargo build`以及`cargo build --release`,生成对应的扩展库。 126 | 127 | ## 编写PHP代码 128 | 129 | 编写test-ffi.php 130 | ```php 131 | fib(12); 164 | } 165 | 166 | echo '[Rust]Debug执行时间:' . (microtime(true) - $time_start).PHP_EOL; 167 | 168 | // release模式 169 | $ffiRelease = FFI::cdef( 170 | "int32_t fib(int32_t n);", 171 | "r2p-fib/target/release/libr2pfib.$libExtension"); 172 | 173 | $time_start = microtime(true); 174 | for ($i=0; $i < 1000000; $i++) { 175 | $ffiRelease->fib(12); 176 | } 177 | 178 | echo '[Rust]Release执行时间:' . (microtime(true) - $time_start).PHP_EOL; 179 | ?> 180 | ``` 181 | 182 | ## 查看测试结果 183 | 184 | 执行`make test-php`查看结果,看看惊不惊喜,意不意外。 185 | 186 | ``` 187 | [PHP]fib执行时间:3.624843120575 188 | [Rust]Debug执行时间:2.0988941192627 189 | [Rust]Release执行时间:0.56653189659119 190 | ``` 191 | 192 | ## 字符串测试 193 | 194 | 我们编写了一个生成字符串的调用测试,详细可以参考`test-ffi2.php`。 195 | 196 | 这个例子告诉我们,这里频繁调用字符串解析是比较消耗性能的。 197 | 198 | ``` 199 | [Rust]debug执行时间:7.8468458652496 200 | string(52) "💣na na na na na na na na na na na na Batman! 💣" 201 | [Rust]release执行时间:1.4546401500702 202 | string(52) "💣na na na na na na na na na na na na Batman! 💣" 203 | [PHP]执行时间:0.23888492584229 204 | string(52) "💣na na na na na na na na na na na na Batman! 💣" 205 | ``` 206 | 207 | ## 总结 208 | 209 | 目前PHP同Rust结合有几种方式:1.PHP的FFI调用Rust导出的库;2.编写PHP扩展调用Rust动态库;3.直接采用Rust编写PHP扩展库; 210 | 211 | 第一种目前并不成熟,而且受限于PHP7.4+,所以不是很推荐,仅限于兴趣研究,另外FFI性能损耗也是比较大,频繁调用处理非常不推荐。 212 | 213 | 第二种比较实用,如果想为PHP添加一些额外扩展也是非常推荐这种方式,将Rust开发的库按照格式导出再交给PHP调用处理。 214 | 215 | 第三种基本上需要将PHP的C API导出为Rust方法,然后直接在Rust中编写PHP的扩展,这种方式目前还没尝试。 216 | 217 | --------------------------------------------------------------------------------