如何用 Rust 來為 PHP 助力

上週,一篇關於《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/


分享到:


相關文章: