Categories
PHP

Supercharge PHP using FFI

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.

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.

not ffi

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.

This extension allows the loading of shared libraries (.DLL or .so), calling of C functions, and accessing of C data structures in pure PHP, without having to have deep knowledge of the Zend extension API, and without having to learn a third “intermediate” language. The public API is implemented as a single class FFI with several static methods (some of them may be called dynamically), and overloaded object methods, which perform the actual interaction with C data.

PHP Docs

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.

Building library

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

php ffi

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

Lower is better

OK, I know you want to see the full benchmark details which is not possible by the above image. Here is the data

Fibonacci NumPHPPHP + FFI (Rust Unoptimized)PHP + FFI (Rust Optimized)Rust UnoptimizedRust Optimized
250.0072260830.0008689160.0002020420.0009953750.0003365
300.0647273330.007858250.0020684170.0094185410.003451542
350.6399763750.0550800420.0206387090.0565274160.028987375
407.1679136250.4056158330.1807733340.4048400830.187686709
4580.191195214.467658251.9566484.4611893341.962651
All times are calculated in seconds

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.

By Mehedi Hasan

Hi!
I am Mehedi Hasan, work as a Software Engineer at weDevs. I love to experiment with the latest tech and make something that solves some problems of others and mine. Also in my free time, I like to read religious books on Islam.

2 replies on “Supercharge PHP using FFI”

আপনি robust বলতে rust বুঝিয়েছেন? যদি তাই হয় তাহলে, উত্তরঃ PHP ফাইল এ rust লেখা যাবে না । আপনি Rust কোড কে কম্পাইল করে ব্যবহার করতে পারবেন যেটি এখানে দেখানো হয়েছে।

Leave a Reply

Your email address will not be published. Required fields are marked *