On Fri, Aug 2, 2024, at 18:51, Ilija Tovilo wrote:
> Hi everyone
> 
> As you probably know, a common performance optimization in PHP is to
> prefix global function calls in namespaced code with a `\`. In
> namespaced code, relative function calls (meaning, not prefixed with
> `\`, not imported and not containing multiple namespace components)
> will be looked up in the current namespace before falling back to the
> global namespace. Prefixing the function name with `\` disambiguates
> the called function by always picking the global function.
> 
> Not knowing exactly which function is called at compile time has a
> couple of downsides to this:
> 
> * It leads to the aforementioned double-lookup.
> * It prevents compile-time-evaluation of pure internal functions.
> * It prevents compiling to specialized  opcodes for specialized
> internal functions (e.g. strlen()).
> * It requires branching for frameless functions [1].
> * It prevents an optimization that looks up internal functions by
> offset rather than by name [2].
> * It prevents compiling to more specialized argument sending opcodes
> because of unknown by-value/by-reference passing.
> 
> All of these are enabled by disambiguating the call. Unfortunately,
> prefixing all calls with `\`, or adding a `use function` at the top of
> every file is annoying and noisy. We recently got a feature request to
> change how functions are looked up [3]. The approach that appears to
> cause the smallest backwards incompatibility is to flip the order in
> which functions are looked up: Check in global scope first, and only
> then in local scope. With this approach, if we can find a global
> function at compile-time, we know this is the function that will be
> picked at run-time, hence automatically enabling the optimizations
> above. I created a PoC implementing this approach [4].
> 
> Máté has kindly benchmarked the patch, measuring an improvement of
> ~3.9% for Laravel, and ~2.1% for Symfony
> (https://gist.github.com/kocsismate/75be09bf6011630ebd40a478682d6c17).
> This seems quite significant, given that no changes were required in
> either of these two codebases.

So, what you’re saying is that symfony and laravel can get a performance 
increase by simply adding a \ in the right places? Why don’t they do that 
instead of changing the language?

> 
> There are a few noteworthy downsides:
> 
> * Unqualified calls to functions in the same namespace would be
> slightly slower, because they now involve checking global scope first.
> I believe that unqualified, global calls are much more common, so this
> change should still result in a net positive. It's also possible to
> avoid this cost by adding a `use function` to the top of the file.

For functions/classes in the same exact namespace, you don’t need a use 
statement. But after this change, you do in certain cases?

namespace Foo;

function array_sum($bar) {}

function baz($bar) {
  return array_sum($bar);
}

So, how do you use that function in the same file?

> * Introducing new functions in the global namespace could cause a BC
> break for unqualified calls, if the function happens to have the same
> name. This is unfortunate, but likely rare. Since new functions are
> only introduced in minor/major versions, this should be manageable,
> but must be considered for every PHP upgrade.

We can only see open source code when doing impact analysis. This means picking 
even a slightly “popular” name could go very poorly.

> * Some mocking libraries (e.g. Symfony's ClockMock [5]) intentionally
> declare functions called from some file in the files namespace to
> intercept these calls. This use-case would break. That said, it is
> somewhat of a fragile approach to begin with, given that it wouldn't
> work for fully qualified calls, or unnamespaced code.

See above. I’ve seen this “trick” used on many closed source projects. I’ve 
also seen it used when PHP has a bug and the workaround is to implement it in 
php like this. 

> 
> I performed a small impact analysis [6]. There are 484 namespaced
> functions shadowing global, internal functions in the top 1000
> composer packages. However, the vast majority (464) of these functions
> come from thecodingmachine/safe, whose entire purpose is offering
> safer wrappers around internal functions. Excluding this library,
> there are only 20 shadowing functions, which is surprisingly little.
> Furthermore, the patch would have no impact on users of
> thecodingmachine/safe, only on the library code itself.
> 
> As for providing a migration path: One approach might be to introduce
> an INI setting that performs the function lookup in both local and
> global scope at run-time, and informs the user about the behavioral
> change in the future. To mitigate it, an explicit `use function` would
> need to be added to the top of the file, or the call would need to be
> prefixed with `namespace\`. The impact analysis [6] also provides a
> script that looks for shadowing functions in your project. It does not
> identify uses of these functions (yet), just their declarations.
> 
> Lastly, I've already raised this idea in the PHP Foundations internal
> chat but did not receive much positive feedback, mostly due to fear of
> the potential BC impact. I'm not particularly convinced this is an
> issue, given the impact analysis. Given the surprisingly large
> performance benefits, I was inclined to raise it here anyway. It also
> sparked some related ideas, like providing modules that lock
> namespaces and optimize multiple files as a singular unit. That said,
> such approaches would likely be significantly more complex than the
> approach proposed here (~30 lines of C code).
> 
> Anyway, please let me know about possible concerns, broken use-cases,
> or any alternative approaches that may come to mind. I'm looking
> forward to your feedback.
> 
> Ilija
> 
> [1] https://github.com/php/php-src/pull/12461
> [2] https://github.com/php/php-src/pull/13634
> [3] https://github.com/php/php-src/issues/13632
> [4] https://github.com/php/php-src/pull/14529
> [5] 
> https://github.com/symfony/symfony/blob/7.1/src/Symfony/Bridge/PhpUnit/ClockMock.php
> [6] https://gist.github.com/iluuu1994/4b83481baac563f8f0d3204c697c5551
> 

— Rob

Reply via email to