As we all know that PHP is an open-source general-purpose scripting language. It is a type of simple and easy-to-write language which is good, but sometimes it brings some performance penalty.
In this article, we are trying to remove those performance penalties using FFI (Foreign Function Interface). So, let’s get started.
Today’s challenge is to make a Fibonacci number generator.
Let’s try to solve that using PHP
Suppose we are trying to calculate the Fibonacci number in PHP, and here is the simple Fibonacci number generator function below.
function fib(int $n) {
if ($n === 1 || $n === 2) {
return 1;
}
return fib($n - 1) + fib($n - 2);
}
Above, we have created a function named fib
that takes one number argument and returns its Fibonacci value.
Let’s create a PHP file and execute that function in the terminal using PHP binary prefix with the time
command, So we can get the elapsed time of the executed script.
print_r(fib(20));
After running our script we get the following output in our terminal.
Above, 6765
is the Fibonacci value of 20
, And the total elapsed time is 0.033s
.
That was quick! let’s give the higher number to fib
function. This time we give 40 to fib
function.
print_r(fib(40));
After running the script we get the following output in our terminal.
As you know from the previous example, this time we get 102334155
the value of fib(40)
, and the total elapsed time is 7.100s
.
Let’s try now using FFI in PHP
First, I should tell you that FFI is not magic that magically boosts your code. FFI is a PHP extension and this is the following definition from the PHP website.
So we need to learn the C language, right? The answer is No. You could write using C but alternatively, you could use another language and compile into C style shared library. In our example, we will use Rust programming language. So let’s code.
First, we need to create a library project. In rust, we will use Cargo to handle it.
Now write the rust implementation of the Fibonacci number generator function.
fn fib(n: i64) -> i64 {
return match n {
1 | 2 => 1,
n => fib(n - 1) + fib(n - 2)
}
}
OK, now tell the compiler fib
function should expose for FFI.
pub extern "C" fn fib(n: i64) -> i64 {
return match n {
1 | 2 => 1
n => fib(n - 1) + fib(n - 2)
}
}
For more information, click here
Before starting to compile our code, we should tell one more thing to Rust compiler, stop mangling the function name by using #[no_mangle]
attribute.
#[no_mangle]
pub extern "C" fn fib(n: i64) -> i64 {
return match n {
1 | 2 => 1
n => fib(n - 1) + fib(n - 2)
}
}
And now we have to tell the rust compiler, the output binary should be a dynamic system library. So, it will produce *.dll
file for Windows, *.dylib
file for macOS and *.so
file for Linux.
[lib]
crate-type=["cdylib"]
We need to add the two lines above to Cargo.toml
file.
Now we need to compile our rust code. Just run the build command of cargo.
This will produce the binary file to the ffi>target>debug
directory. I am on macOS So, In my case, my code was compiled into the *.dylib
file.
Now we will use the file with our PHP code using FFI.
$ffi = FFI::cdef('long long fib(long long n);', __DIR__ . '/ffi/target/debug/libffi.dylib');
print_r($ffi->fib(20));
In this example, we use the cdef
method of PHP FFI
class to load our library file. In C you have to define the function signature before the main
function, If your function is declared after the main
function. In that case, you have to declare the function definition on the first parameter of the cdef
method, and the second parameter will take the path of the library file. And it will give us the exposed function as a method. In that case, the method name is the fib
.
long long
is a type that is used in C to represent 64-bit numbers.
So, run the first example, where n=20
And surprisingly it took 0.046s
to complete. Whereas PHP implementation was took only 0.36s
.
Let’s try on our second example, where n=40
Aaha! It took just 0.453s
to complete. Whereas PHP implementation is taken 7.1s
to complete.
At this moment you may be thinking this is the end of the comparison. But we haven’t unveiled our main power, the RELEASE BUILD.
PHP + FFI (RUST Release Build)
In this step, we are going to tweak some build configurations to tell the Rust compiler, This build is for production use so try to optimize our binary as much as possible. For this reason, we need to add the following configuration lines to Cargo.toml
file.
[profile.release]
opt-level = 'z'
lto = true
codegen-units = 1
Now build the library using --release
flag
This will create a new directory named release
and compile the library into that directory. So we need to modify the library path into the FFI loading PHP code. And then try to execute it to n=40
.
This time it took only 0.222s
. That is half of the previous elapsed time of FFI + Rust Unoptimized.
Now let’s see the overall results in a chart
OK, I know you want to see the full benchmark details which is not possible by the above image. Here is the data
Fibonacci Num | PHP | PHP + FFI (Rust Unoptimized) | PHP + FFI (Rust Optimized) | Rust Unoptimized | Rust Optimized |
25 | 0.007226083 | 0.000868916 | 0.000202042 | 0.000995375 | 0.0003365 |
30 | 0.064727333 | 0.00785825 | 0.002068417 | 0.009418541 | 0.003451542 |
35 | 0.639976375 | 0.055080042 | 0.020638709 | 0.056527416 | 0.028987375 |
40 | 7.167913625 | 0.405615833 | 0.180773334 | 0.404840083 | 0.187686709 |
45 | 80.19119521 | 4.46765825 | 1.956648 | 4.461189334 | 1.962651 |
Conclusion
As you see from the examples, Simple computation is fine for PHP. Do not overengineer the simple tasks with complex steps, Like using FFI or building a native PHP extension, etc. Use them wisely and thanks for reading the entire article.
2 replies on “Supercharge PHP using FFI”
Vai, Php file ki robust use kora jabe??Mane javascript and php jemon aksathe likha jai??
আপনি robust বলতে rust বুঝিয়েছেন? যদি তাই হয় তাহলে, উত্তরঃ PHP ফাইল এ rust লেখা যাবে না । আপনি Rust কোড কে কম্পাইল করে ব্যবহার করতে পারবেন যেটি এখানে দেখানো হয়েছে।