上週,一篇關於《PHP 兼 Zend 聯合創始人 Zeev Suraski 宣佈從 Zend 離職》的文章在中國程序員中炸開了鍋。文章中的Zend創始人離職可謂引起軒然大波,各種對PHP不友好的聲音也被傳遞了出來。雖然來自中國的PHP核心開發者鳥哥發聲,但許多程序員對PHP的態度並不是很樂觀。
作為我在Web領域的入門語言,對PHP的感情猶如初戀一般美好。但伴隨著近幾年來大家對PHP各種調侃、諷刺,加上新語言Go、Rust、Javascript等強勢崛起。似乎讓“我是一名PHP程序員”變得底氣很不足。來源於Web、輝煌於Web、受限於Web,不得不說,這讓腳本語言PHP陷入了困境,因為Web已經不再是那個靠渲染網頁的PC時代,雖然目前有Swoole這樣優秀的異步方案,但也很難讓我對PHP提起足夠的興趣。
Rust作為近幾年來逐步流行的新興語言,憑藉安全、高效、可靠,開始進入了人們的視野。週末,重拾起了PHP,想看看是否能夠在PHP和Rust之間擦出一點點火花。當然,只是出於個人興趣做的一些實驗,這並不能為兩個優秀的語言帶來什麼改變。但我想,至少能夠讓大家對PHP和Rust有更多的瞭解。
PHP7.4中的FFI
起初,PHP7.4中的新特性FFI引起我很大的興趣,因為在很多語言中已經擁有了這樣的特性,例如:Python、NodeJS、Ruby等,PHP首次的引入,讓我覺得PHP團隊正在朝著這方面進行努力。
我首先採用Rust編寫了一個Fibonacci斐波那契數列,這裡實現了一個簡單的計算函數,我將他導出為動態鏈接庫,以供PHP的FFI調用。
#[no_mangle] pub extern fn fib(n: i32) -> i32 { return match n { 1 | 2 => 1, n => fib(n - 1) + fib(n - 2) } }
編寫完成後,我嘗試在PHP的FFI中進行調用,為了想對比下性能,我同時編寫了一個PHP的函數:
// 一個PHP的fib函數 function fib($n) { if ($n === 1 || $n === 2) { return 1; } else { return fib($n-1) + fib($n-2); } }
接下來,我在PHP中調用他們,為了能夠看出性能差異,我將調用1000000次:
// release模式 $ffiRelease = FFI::cdef( "int32_t fib(int32_t n);", "r2p-fib/target/release/libr2pfib.$libExtension"); $time_start = microtime(true); for ($i=0; $i < 10000000; $i++) { $v = $ffiRelease->fib(12); } echo '[Rust]Release執行時間:' . (microtime(true) - $time_start).PHP_EOL;
從測試結果來看,Rust的FFI結果是讓人驚喜的。
PHP的計算耗時30秒以上,Rust僅僅用了6秒。
當我為此欣喜若狂的時候,我又嘗試了下PHP的FFI調用生成字符串,在PHP中是類似這樣一個方法:
function text_generate($num) { $result = ""; $result .= str_repeat("na ",$num); $result .= "Batman! "; return $result; }
結果Rust由於在PHP的FFI中間字符串轉換的損耗,性能並沒有達到預想那樣。
PHP擴展調用Rust動態庫
因為第一個操作,讓我想到了FFI在多次調用性能損耗是很大的,這時我想實現在PHP擴展中來調用Rust動態庫。
同時為了對比,我編寫一個C的Fib函數進行比較。
我創建了一個名為rust的PHP擴展,完成了關於我們上面編寫的Rust函數的調用。
ZEND_BEGIN_ARG_INFO(arginfo_rust_fib, 0) ZEND_ARG_INFO(0, number) ZEND_END_ARG_INFO() /* {{{ int rust_fib( [ int $var ] ) */ PHP_FUNCTION(rust_fib) { zend_long number = 0; zend_long result = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(number) ZEND_PARSE_PARAMETERS_END(); if (number == 0) { RETURN_LONG(result); } else { result = fib(number); RETURN_LONG(result); } } /* }}}*/
結果同樣讓我驚喜,沒有了FFI,它確實提升了20%左右的性能。但很明顯,它的實現複雜度更高了。
採用Rust編寫PHP擴展
將PHP的Zend API結構導出,直接在Rust中實現PHP模塊的編寫。雖然自己沒有足夠的精力去做這樣的嘗試,但我覺得這是一個可行的方法。我想在未來的時間內完成這樣的嘗試。
總結
雖然影響PHP更好的發展的主要因素並不是性能,儘管本篇只是從性能提升入手對PHP進行了測試,但我覺得,作為一個擁有如此龐大開發群體的Web語言,也是時候需要跟Go、Rust、Node這些新興語言學習了,建立並不侷限於Web領域的新的生態體系,大膽的去做一些新的嘗試,讓PHP不再是“世界第一語言”。
本文完整測試代碼及結果請參考:https://github.com/llgoer/php-ffi-rust/