Why does a backslash prefix improve PHP function call performance

Aug 29, 2023 by Jeroen Deviaene

Have you ever noticed developers putting backslashes (\) in front of their PHP function calls? Many developers (including myself) do this, however few developers actually know why. It’s often referred to as a micro-optimization, but how could such a simple thing as a backslash improve the performance of your application?

PHP namespaces

Introduced in PHP version 5.3, namespaces serve as a method to organize elements like classes and functions within an application. They function similarly to folders in a computer’s file system, providing distinct containers for code and preventing naming conflicts. In modern PHP, all files are usually part of a namespace that is mapped to the folder structure of the application.

To access a class in a certain namespace, we start with the root namespace (\) and then add all parent namespaces separated by \. For example:

$user = new \App\Model\User();

By doing this, wherever we use this object, the interpreter knows where to find this class.

The backslash prefix

Just like classes, functions can also be put into namespaces. If we were to define a hello() function in the App\Model namespace, we should call it like this:

\App\Model\hello();

So, when adding a single backslash before a function call, you are telling the interpreter to search for the function directly in the root namespace. Coincidentally, this is where most of the built-in functions for the PHP language live.

Opcode

Opcode is the intermediate language the PHP interpreter converts your code to before executing. By converting our code manually we can learn a lot about how PHP handles our code. Using tools like 3v4l, we can convert our function calls to opcode and see the difference with or without the backslash.

Let’s start by calling a native PHP function from a namespace using a backslash prefix:

namespace Foo\Bar;

echo \strtoupper('Hello World');
op          return  operands
---------------------------------
INIT_FCALL          'strtoupper'
SEND_VAL            'Hello+World'
DO_ICALL    $0
ECHO                $0

As you can see in the opcodes above, we simply do a function call to strtoupper with the string "Hello World" as its parameter and echo the result.

Now what happens if we remove the backslash from the function call?

namespace Foo\Bar;

echo strtoupper('Hello World');
op                     return  operands
--------------------------------------------------------
INIT_NS_FCALL_BY_NAME          'Foo%5CBar%5Cstrtoupper'
SEND_VAL_EX                    'Hello+World'
DO_FCALL               $0
ECHO                           $0

Without the backslash, the interpreter added the current namespace to the function name. Yet the function does not exist in this namespace, so how does that work? Well, functions have a special case that when not found in the current namespace, the interpreter will look for it in the root namespace.1 This is what the special opcode INIT_NS_FCALL_BY_NAME does.
This does add a little overhead because the interpreter might have to look for the function twice. And remember that most functions that you call are located in the root namespace.

So why doesn’t the interpreter search for the function before it creates the opcodes? Well, this is impossible because functions could be defined dynamically at runtime. This makes it impossible for the interpreter to be sure that that is the function you meant to call.

Even more optimization

Some built-in functions can benefit even more from adding a backslash prefix. Certain functions in PHP are linked directly to an opcode, so if the interpreter knows for sure that’s the function you are calling, it can replace the function call directly with the associated opcode.

Take for example the strlen function. On the left is the opcode without the backslash and on the right the backslash was added.

strlen($a);
\strlen($a);
op                     return  operands
----------------------------------------
INIT_NS_FCALL_BY_NAME          'Foo%5CBar%5Cstrlen'
SEND_VAR_EX                    !0
DO_FCALL               $1
op      return  operands
------------------------
STRLEN  ~1      !0

More examples of functions that have an underlying opcode are count(), in_array(), and array_key_exists(). Also, type comparing functions benefit from this, functions such as is_null(), is_bool(), is_string(), etc. Lastly, reflection functions like get_class(), func_get_args() and function_exist() also have their own opcodes.

Conclusion

While adding a backslash prefix to function calls maybe is yet another micro-optimization, think about how many function calls happen for every request in your application. If you use a framework you’ll probably hit 100 way sooner than you think.

It is not my intention to have you update all function calls in your projects, there’s plenty of automated tools to do that.2
But in my opinion, it’s always beneficial to know what syntax is available and why it does what it does. I enjoy digging deeper into how programming languages work and hope you also learned something today.