You may have seen namespaced PHP code where global functions have been imported via
use function or have been fully-qualified using a
\ character. Or perhaps you use a static analysis tool that routinely bugs you to import global functions and you don’t really understand why. In this short article, I’m going to explain the “why” behind this particular micro-optimisation.
I’ve used the
str_repeat function in the example below but it could have been another internal PHP function or a globally scoped function declared in something like a
I’m going to run the code snippet through an Opcode dumper called Vulcan Logic Dumper (VLD). If you’d like to play around with VLD without installing it on your local environment, you can easily run it using 3v4l.org. After you
eval(); your code, you should see a tab called ‘VLD’ at the top of the results box. You can also look at the performance metrics of the executed code by using the ‘Performance’ tab.
In the Opcode dump below, we can see that on line 0 that there’s an
INIT_FCALL Opcode for the invocation of
str_repeat. On lines 1 and 2, there are
SEND_VAL Opcodes on for passing the arguments to the function.
You might think that this is how it always looks when you call a function such as
str_repeat within namespaced code. However, that
INIT_FCALL Opcode is only being used here because of the explicit importing of
str_repeat function from the global namespace. This would also be the case if you removed the
use function statement and called the function as
\str_repeat('teststring', 3) instead.
If I run that snippet again without the import, I get slightly different results this time. The Opcode on line 0 in the below dump is now
INIT_NS_FCALL_BY_NAME rather than
INIT_FCALL. This means that PHP is looking for the function
str_repeat inside the namespace
Foo\Bar\Baz, which we know doesn’t exist. It will then traverse the namespace tree (
Foo) until it hits the global namespace and finds the internal PHP function
str_repeat. The performance implications of this are hard to quantify and will vary greatly depending on your application, but I think we can all agree that fewer operations to produce the same result is preferable.
Avoiding namespace traversal is just one reason to import global functions within namespaced code. I’ll explore the other reason by calling the
strlen function with an explicit import; I’ve made a crude example function called
isGreaterThanFiveCharacters that returns whether an ASCII string is greater than 5 characters or not.
On line 1 of the below Opcode dump, the Opcode is
STRLEN rather than
INIT_FCALL. This is because
strlen is one of a limited number of internal PHP functions that have their own Opcodes. The
STRLEN Opcode means that rather than the Zend Engine having to call a function to calculate the length of the string, it can instead access the
len property from the zend_string struct. To my knowledge there isn’t an official list of the internal functions that have an accompanying Opcode, but this method from PHP-CS-Fixer looks to cover them all.
What if I was to run the
isGreaterThanFiveCharacters function again, but this time without importing the
I now get the
INIT_NS_FCALL_BY_NAME Opcode instead of
STRLEN, followed by
DO_FCALL. Huh, what’s going on here? Well, it turns out that internal PHP functions with accompanying Opcodes are only able to utilise them inside a namespace if the function has been imported or has been fully-qualified. This mechanic doesn’t apply to non-namespaced code, where the aforementioned functions will always use their accompanying Opcodes.
If you were to call that
isGreaterThanFiveCharacters function hundreds of thousands of times in a performance sensitive loop inside namespaced code without having imported
strlen, you’re going to suffer two performance penalties. The first being that your application is going to spend time needlessly traversing namespaces to find
strlen. The second is that PHP will have to invoke the
strlen function each time, when it could get the
len property from the
zend_string struct instead, and skip the function call entirely.