# [Concept] Flip relative function lookup order (global, then local)

- Thread: https://externals.io/message/124718
- Original archive: https://news-web.php.net/php.internals/124718
- Started by Ilija Tovilo on 2024-08-02T16:51:33+00:00
- Messages: 109
- Page 1 of 5: messages 1 to 25 (25 per page, add `?page=N` to the URL)
- Next page: https://externals.io/message/124718.md?page=2
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #124718 — Ilija Tovilo — 2024-08-02T16:51:33+00:00

URL: https://externals.io/message/124718#124718

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.

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.
* 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.
* 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.

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

---

## #124719 — Rob Landers — 2024-08-02T17:08:36+00:00

URL: https://externals.io/message/124718#124719 · In reply to: #124718

> 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

---

## #124720 — Rob Landers — 2024-08-02T17:14:56+00:00

URL: https://externals.io/message/124718#124720 · In reply to: #124718

> 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).

There was an entire thread about modules and packages and shenanigans not too long ago. It’s rather fascinating. Highly recommend participating or starting a new thread. It seems that people are interested in it, and want it. 

— Rob

---

## #124721 — Nick Lockheart — 2024-08-02T17:19:41+00:00

URL: https://externals.io/message/124718#124721 · In reply to: #124718

> 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]. 

I think there should be some way to use globals first at compile time.

I had suggested a per-file directive in a post to this list a while
back. Something like:

namespace foo;
use global functions;

class MyClass {

 // do stuff.

}


Where `use global functions` would be a special token that the compiler
uses to skip the ns lookup and use dedicated opcodes when available.

---

## #124722 — Thomas Nunninger — 2024-08-02T17:49:54+00:00

URL: https://externals.io/message/124718#124722 · In reply to: #124718

Hi,

Am 02.08.24 um 18:51 schrieb Ilija Tovilo:

...
> 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.
> * 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.
> * 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.

Similar to Symfony's ClockMock this "feature" was propagated some years 
ago to e.g. intercept calls to the file system when running tests where 
the application was not designed with test-ability in mind.

Regards,
Thomas

---

## #124723 — Rowan Tommins [IMSoP] — 2024-08-02T17:53:50+00:00

URL: https://externals.io/message/124718#124723 · In reply to: #124721

>I had suggested a per-file directive in a post to this list a while
>back. Something like:
>
>namespace foo;
>use global functions;

There was a proposal for exactly this a few years ago, which ended up in an RFC with a slightly different syntax (using a declare() statement), but was declined in voting by 35 votes to 2.

I can't remember much about the discussion, so am not sure what changes would make a new attempt more likely to pass.

Regards,
Rowan Tommins
[IMSoP]

---

## #124724 — Nick Lockheart — 2024-08-02T18:46:03+00:00

URL: https://externals.io/message/124718#124724 · In reply to: #124723

> 
> 
> On 2 August 2024 18:19:41 BST, Nick Lockheart <lists@ageofdream.com>
> wrote:
> > […]
> 
> There was a proposal for exactly this a few years ago, which ended up
> in an RFC with a slightly different syntax (using a declare()
> statement), but was declined in voting by 35 votes to 2.
> 
> I can't remember much about the discussion, so am not sure what
> changes would make a new attempt more likely to pass.
> 
> Regards,
> Rowan Tommins
> [IMSoP]

In all likelihood, it was the syntax that was disfavored.

What about an RFC where we vote on if the feature should exist, without
any syntax?

ie. "Should there be a way for developers to signal to the parser that
all functions should be treated as global and skip NS lookup, and use
dedicated opcodes? The specific syntax would be decided in a different
RFC/vote if this one passes."

Yes: We should do this, let's discuss syntax possibilities.
No: This should not be a feature at all.

---

## #124725 — Claude Pache — 2024-08-02T19:01:47+00:00

URL: https://externals.io/message/124718#124725 · In reply to: #124718

Hi,

I propose the following alternative approach:

* establish a restricted whitelist of global functions for which the performance gain would be noteworthy if there wasn’t any need to look at local scope first;

* for those functions, disallow to define a function of same name in any namespace, e.g.: https://3v4l.org/RKnZt

That way, those functions could be optimised, but the current semantics of namespace lookup would remain unchanged.

—Claude

---

## #124726 — Rowan Tommins [IMSoP] — 2024-08-02T20:30:42+00:00

URL: https://externals.io/message/124718#124726 · In reply to: #124724

> There was a proposal for exactly this a few years ago, which ended up
> in an RFC with a slightly different syntax (using a declare()
> statement), but was declined in voting by 35 votes to 2.

Sorry, I forgot the link: https://wiki.php.net/rfc/use_global_elements





>In all likelihood, it was the syntax that was disfavored.

There was a lot of discussion beforehand about the syntax, and the RFC attempts to summarise some of it, but skimming through the voting thread <https://externals.io/message/108306> I don't think that was what made it fail. The objections seem to be mostly about the general approach, not the details. 


Rowan Tommins
[IMSoP]

---

## #124727 — Bilge — 2024-08-02T20:37:23+00:00

URL: https://externals.io/message/124718#124727 · In reply to: #124718

Hi Ilija,

I think this proposal has legs, and you are right to rekindle it, 
instead of letting it die quietly.


> * 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.
>
My only concern is there needs to be an alternative way to do this: 
intercepting internal calls. Sometimes, whether due to poor architecture 
or otherwise, we just need to be able to replace an internal function 
call. One example I can think of recently is where I had to replace 
`header()` with a void function in tests, just to stop some legacy code 
emitting headers before the main framework kicked in, then unable to 
emit its own response because HTTP headers had already been sent. In a 
perfect world it shouldn't be necessary, but sometimes it is, so I think 
for this proposal to be palpable there must still be a way to achieve this.

Cheers,
Bilge

---

## #124728 — Nick Lockheart — 2024-08-02T22:00:33+00:00

URL: https://externals.io/message/124718#124728 · In reply to: #124727

> Hi Ilija,
> I think this proposal has legs, and you are right to rekindle it,
> instead of letting it die quietly.
> 
>  
> > […]
> My only concern is there needs to be an alternative way to do this:
> intercepting internal calls. Sometimes, whether due to poor
> architecture or otherwise, we just need to be able to replace an
> internal function call. One example I can think of recently is where
> I had to replace `header()` with a void function in tests, just to
> stop some legacy code emitting headers before the main framework
> kicked in, then unable to emit its own response because HTTP headers
> had already been sent. In a perfect world it shouldn't be necessary,
> but sometimes it is, so I think for this proposal to be palpable
> there must still be a way to achieve this.
> Cheers,
>  Bilge
>  


I was thinking about a similar problem this week.

If class A relies on class B, but you want to swap out
class B with a stub to test class A in isolation,
is there a way to make every call to class B,
from class A, actually call a different class
during the test, without modifying class A's code?


Minimal code for discussion purposes:


// conf class in global namespace
abstract class CONF {
   const DATABASE_HOST_NAME = 'db.example.com';
   const DATABASE_NAME = 'production';
   const DATABASE_USER_NAME = 'prod_user';
   const DATABASE_PASSWORD = '123';
}


// conf class in test namespace:
namespace test;
abstract class CONF {
   const DATABASE_HOST_NAME = 'db.sandbox.com';
   const DATABASE_NAME = 'test';
   const DATABASE_USER_NAME = 'test_user';
   const DATABASE_PASSWORD = 'abc';
}


// SQL class in global namespace
class SQL {

   private function Init(){
      self::$oPDO = new PDO(
         'mysql:host='.CONF::DATABASE_HOST_NAME.
         ';dbname='.CONF::DATABASE_NAME.';charset=utf8mb4',
         CONF::DATABASE_USER_NAME,
         CONF::DATABASE_PASSWORD,
         []
      );
   }
}

// Testing class in test namespace:
namespace test;
class SQLTester {

   // How do I make the SQL class see \test\CONF instead of
   // \CONF, when SQL calls for CONF in this test scope,
   /// without changing anything inside of the SQL class?
}


I think some kind of sandboxing tools would be useful for
build/test/deployment.

---

## #124729 — Christoph M. Becker — 2024-08-02T22:48:27+00:00

URL: https://externals.io/message/124718#124729 · In reply to: #124728

> I think some kind of sandboxing tools would be useful for
> build/test/deployment.

There are uopz[1] and runkit7[2] available on PECL which can be used to
unit-test untestable code (and more), but you are likely better off to
refactor such code sooner than possible, since such extensions may
easily break for new minor PHP versions (and occasionally, such breaks
may not be fixable at all[3]), and often are completely broken for new
major PHP versions (uopz got a completely different API for PHP 7, and
runkit was even provided as new extension named runkit7).  And
maintainig such extensions is a PITA[4], and as such, compatibility with
new PHP versions may not be available when you need it.

[1] <https://pecl.php.net/package/uopz>
[2] <https://pecl.php.net/package/runkit7>
[3] <https://github.com/krakjoe/uopz/issues/176>
[4] <https://github.com/zenovich/runkit/issues/87>

Cheers,
Christoph

---

## #124730 — Deleu — 2024-08-02T22:48:38+00:00

URL: https://externals.io/message/124718#124730 · In reply to: #124728

> I was thinking about a similar problem this week.
>
> If class A relies on class B, but you want to swap out
> class B with a stub to test class A in isolation,
> is there a way to make every call to class B,
> from class A, actually call a different class
> during the test, without modifying class A's code?
>
>
> Minimal code for discussion purposes:
>
>
> // conf class in global namespace
> abstract class CONF {
>    const DATABASE_HOST_NAME = 'db.example.com';
>    const DATABASE_NAME = 'production';
>    const DATABASE_USER_NAME = 'prod_user';
>    const DATABASE_PASSWORD = '123';
> }
>
>
> // conf class in test namespace:
> namespace test;
> abstract class CONF {
>    const DATABASE_HOST_NAME = 'db.sandbox.com';
>    const DATABASE_NAME = 'test';
>    const DATABASE_USER_NAME = 'test_user';
>    const DATABASE_PASSWORD = 'abc';
> }
>
>
> // SQL class in global namespace
> class SQL {
>
>    private function Init(){
>       self::$oPDO = new PDO(
>          'mysql:host='.CONF::DATABASE_HOST_NAME.
>          ';dbname='.CONF::DATABASE_NAME.';charset=utf8mb4',
>          CONF::DATABASE_USER_NAME,
>          CONF::DATABASE_PASSWORD,
>          []
>       );
>    }
> }
>
> // Testing class in test namespace:
> namespace test;
> class SQLTester {
>
>    // How do I make the SQL class see \test\CONF instead of
>    // \CONF, when SQL calls for CONF in this test scope,
>    /// without changing anything inside of the SQL class?
> }
>
>
> I think some kind of sandboxing tools would be useful for
> build/test/deployment.
>

You could hack this out using the autoloader, but it's something that the
PHP community frowns upon, imo. A much prevalent practice in the PHP
ecosystem is a Dependency Injection container. A somewhat similar concept
exists in the Javascript ecosystem with hoisting import statements and
mocking modules, but if you don't understand the system and are unaware
that order of import execution will matter on whether the mock succeeds or
not plays a huge role in making it a cumbersome and awkward system.

Regardless, it's not possible for functions as users don't control function
autoloader.
-- 
Marco Deleu

---

## #124731 — Nick Lockheart — 2024-08-04T06:19:18+00:00

URL: https://externals.io/message/124718#124731 · In reply to: #124730

Good morning,

I am writing to request RFC karma for the wiki account with username
`nlockheart`.

I would like to write an RFC for community discussion and
consideration.

Thank you,
Nick Lockheart

---

## #124732 — Christoph M. Becker — 2024-08-04T09:52:30+00:00

URL: https://externals.io/message/124718#124732 · In reply to: #124731

> I am writing to request RFC karma for the wiki account with username
> `nlockheart`.
>
> I would like to write an RFC for community discussion and
> consideration.

RFC karma granted.  Good luck with the RFC!

Christoph

---

## #124742 — Ilija Tovilo — 2024-08-04T17:41:17+00:00

URL: https://externals.io/message/124718#124742 · In reply to: #124719

Hi Rob


>
> 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?

Nothing, of course. However, a Symfony maintainer has expressed
uninterest in prefixing all internal function calls, including
automated use statements at the top of the file. Even if they did,
most users will not.

> 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?

Yes. But I'm not sure how that's different from today? If there's a
local and global function declared with the same name, and you intend
to call the global one, you'll already need to disambiguate the call
with a \.

With this change, your two options would be to:

* Prefix your calls with namespace\. That's quite ugly, but is the
syntax we currently offer.
* Add a `use array_sum;` to the top of the file.

An explicit use has upsides too. It makes it much more obvious that
the global function is shadowed.

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

Yes, and there are many more than 10 000 composer repositories. An
impact analysis can give you an approximation for breakage, not
absolute numbers.

Ilija

---

## #124743 — Ilija Tovilo — 2024-08-04T17:53:11+00:00

URL: https://externals.io/message/124718#124743 · In reply to: #124725

Hi Claude


>
> I propose the following alternative approach:
>
> * establish a restricted whitelist of global functions for which the performance gain would be noteworthy if there wasn’t any need to look at local scope first;
>
> * for those functions, disallow to define a function of same name in any namespace, e.g.: https://3v4l.org/RKnZt
>
> That way, those functions could be optimised, but the current semantics of namespace lookup would remain unchanged.

That would be an improvement over the status quo. However, if you look
at the bullet points in my original email, while some of the
optimizations apply only to some functions (CTE, custom opcodes and
frameless calls), others apply to all internal, global functions
(double-lookup, lookup by offset, specialized argument passing).
Hence, we may only get a fraction of the benefits by restricting the
optimization to a handful of functions. I also wonder if the impact is
actually bigger, as then there's no workaround for redeclaring the
function, requiring much bigger refactoring.

Ilija

---

## #124746 — Nick Lockheart — 2024-08-04T18:07:27+00:00

URL: https://externals.io/message/124718#124746 · In reply to: #124743

> Hi Claude
> 
> On Fri, Aug 2, 2024 at 9:02 PM Claude Pache <claude.pache@gmail.com>
> wrote:
> > […]
> 
> That would be an improvement over the status quo. However, if you
> look
> at the bullet points in my original email, while some of the
> optimizations apply only to some functions (CTE, custom opcodes and
> frameless calls), others apply to all internal, global functions
> (double-lookup, lookup by offset, specialized argument passing).
> Hence, we may only get a fraction of the benefits by restricting the
> optimization to a handful of functions. I also wonder if the impact
> is
> actually bigger, as then there's no workaround for redeclaring the
> function, requiring much bigger refactoring.
> 
> Ilija

Also, overriding any default function is one of the benefits of name
spacing in the first place. If we say, "built-in functions can't be
overridden, then you are basically saying that all built-in functions
are global.

But there is a valid use case for overriding built-ins. You may want to
disable built-in functionality with a stub for unit testing.

There should probably be a per-file way of setting the default either
way without ns lookups.

use global functions
- or -
use local functions
- or -
omit directive to use dynamic NS lookup for BC.

---

## #124758 — Derick Rethans — 2024-08-05T11:23:50+00:00

URL: https://externals.io/message/124718#124758 · In reply to: #124718

> 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.

That INI setting would control the *warning*, and not the 
*functionlity*, right?

> 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.

I am surprised that it is that much of a performance benefit as well, 
but I am also concerned about the BC impact. But if that isn't too much, 
then I guess we need to consider this, but only for a major version. Not 
something I believe we can change in a 8.x version.

cheers,
Derick

---

## #124766 — Ilija Tovilo — 2024-08-05T13:15:46+00:00

URL: https://externals.io/message/124718#124766 · In reply to: #124758

>
> 
>
> > […]
>
> That INI setting would control the *warning*, and not the
> *functionlity*, right?

Yes, that was my suggestion. First, in a future minor version, an INI
option could be added that would warn when finding both a local and
global function when performing an unqualified function call. The only
reason to hide this behind a setting is to avoid the cost of a double
lookup in production code when one isn't necessary, i.e. when calling
local functions in some namespace.

> I am surprised that it is that much of a performance benefit as well,
> but I am also concerned about the BC impact. But if that isn't too much,
> then I guess we need to consider this, but only for a major version. Not
> something I believe we can change in a 8.x version.

Sure, waiting for 9.0 sounds reasonable if we were to choose this approach.

Ilija

---

## #125064 — drealecs@gmail.com — 2024-08-20T08:01:10+00:00

URL: https://externals.io/message/124718#125064 · In reply to: #124742

> Hi Rob
>
> 
> > […]
> increase by simply adding a \ in the right places? Why don’t they do that
> instead of changing the language?
>
> Nothing, of course. However, a Symfony maintainer has expressed
> uninterest in prefixing all internal function calls, including
> automated use statements at the top of the file. Even if they did,
> most users will not.
>
>
Function lookup in either global or local scope is problematic, and
probably that's why we don't have autoloading for functions yet.
How about we change the language so that in PHP 9.0 there will be a notice
that gets triggered when a fallback to the global namespace gets triggered.
We would upgrade that to a warning in PHP 9.2, and it would end up being an
error on PHP 10 and have a BC break.

I don't think adding a \ to each function call is ugly, that's what we have
for classes, and it works fine; or an use statement.
So, why do we think that after people get used to it, they would still
consider it ugly? Never heard the "ugliness" mentioned for classes.

Now, I know this would be a big BC break, but it brings consistency to the
language and forces everyone to improve their code performance.

If that's not acceptable, then maybe considering all unqualified functions
as belonging to global namespace only might be an alternative solution.
That means that non-global functions must be imported or be fully
qualified, and that would be a BC break as well, but a smaller one.

To sum up, I think we need to remove the fallback behavior, so we can have
better things in the future.
Either keep only local with a bigger BC break but a better language
consistency.
Or keep only global with a smaller BC break.

 Regards,
Alex

---

## #125065 — Nick Lockheart — 2024-08-20T08:41:23+00:00

URL: https://externals.io/message/124718#125065 · In reply to: #125064

> We would upgrade that to a warning in PHP 9.2, and it would end up
> being an error on PHP 10 and have a BC break.
> 
> I don't think adding a \ to each function call is ugly, that's what
> we have for classes, and it works fine; or an use statement.
> 
> So, why do we think that after people get used to it, they would
> still consider it ugly? Never heard the "ugliness" mentioned for
> classes.


Respectfully, I think `\` is ugly for both functions and classes.


> Now, I know this would be a big BC break, but it brings consistency
> to the language and forces everyone to improve their code
> performance.

There should be a directive for this, like:

    namespace foo using global functions;

...which automatically acts as if all functions have a \ in front of
them, unless they are fully qualified.

---

## #125066 — Rob Landers — 2024-08-20T09:01:34+00:00

URL: https://externals.io/message/124718#125066 · In reply to: #125065

> 
> > […]
> 
> 
> Respectfully, I think `\` is ugly for both functions and classes.
> 
> 
> > […]
> 
> There should be a directive for this, like:
> 
>     namespace foo using global functions;
> 
> ...which automatically acts as if all functions have a \ in front of
> them, unless they are fully qualified.
> 

Respectfully, I feel like this gets into the heart of a problem with RFCs, where if someone wants to implement something, they have to solve everyone’s problems.

In this case, there is a problem with performance issues due to multiple lookups (though, I’m not convinced fully), so if someone wants to implement function autoloading, they also have to solve this problem (Gina and I have both independently solved it in various ways).

Personally, I’m of the opinion that if you want performance, you know what to do: fully qualify your names. If you don’t care (which is what I gather from the first email in this thread where maintainers were not willing to change their code), then “deal with it.”

The vast majority of performance issues won’t be caused by function lookups, but by databases and poorly written code. Maybe I am wrong, but I rather like what we currently have, whatever benchmarks have to say on the matter. 

— Rob

---

## #125072 — Levi Morrison — 2024-08-20T15:14:05+00:00

URL: https://externals.io/message/124718#125072 · In reply to: #125064

> To sum up, I think we need to remove the fallback behavior, so we can have better things in the future.
> Either keep only local with a bigger BC break but a better language consistency.
> Or keep only global with a smaller BC break.

I have long been in favor of a larger BC break with better language
consistency. Class lookup and function lookup with respect to
namespaces should be treated the same. The difficulty is getting a
majority of people to vote yes for this. Keep in mind that qualifying
every global function is annoying but probably can be somewhat
automated, and will bring better performance. So again, this improves
the existing code even without upgrading.

Yes, there would be complaints about it. Yes, there are probably some
people or projects who wouldn't upgrade. I don't particularly care, as
there are increasingly more operating systems and companies providing
LTS support for long periods of time. Probably Zend.com will offer LTS
support for the last PHP 8.X release, and possibly there will be some
distro which also has it. I believe it's the right thing to do
because:

 1. It's faster.
 2. It enables function autoloading in a similar manner to class autoloading.
 3. It's more consistent, and simpler to teach and maintain.

It's rare that you get all of these together, often you have to make
tradeoffs within them.

---

## #125074 — Ilija Tovilo — 2024-08-20T17:04:39+00:00

URL: https://externals.io/message/124718#125074 · In reply to: #125072

Hi Levi

On Tue, Aug 20, 2024 at 5:14 PM Levi Morrison
<levi.morrison@datadoghq.com> wrote:
>
> I have long been in favor of a larger BC break with better language
> consistency. Class lookup and function lookup with respect to
> namespaces should be treated the same. The difficulty is getting a
> majority of people to vote yes for this. Keep in mind that qualifying
> every global function is annoying but probably can be somewhat
> automated, and will bring better performance. So again, this improves
> the existing code even without upgrading.
>
> Yes, there would be complaints about it. Yes, there are probably some
> people or projects who wouldn't upgrade. I don't particularly care, as
> there are increasingly more operating systems and companies providing
> LTS support for long periods of time. Probably Zend.com will offer LTS
> support for the last PHP 8.X release, and possibly there will be some
> distro which also has it. I believe it's the right thing to do
> because:
>
>  1. It's faster.
>  2. It enables function autoloading in a similar manner to class autoloading.
>  3. It's more consistent, and simpler to teach and maintain.
>
> It's rare that you get all of these together, often you have to make
> tradeoffs within them.

The approach I originally proposed also solves 1. and 2. (mostly) with
very little backwards incompatibility. Consistency is absolutely
something to strive for, but not at the cost of breaking most PHP
code.

To clarify on 2.: The main issue with function autoloading today is
that the engine needs to trigger the autoloader for every unqualified
call to global functions, given that the autoloader might declare the
function in local scope. As most unqualified calls are global calls,
this adds a huge amount of overhead.

Gina solved this in part by aliasing the local function to the global
one after the first lookup. However, that still means that the
autoloader will trigger for every new namespace the function is called
in, and will also pollute the function table.

Reversing the lookup order once again avoids local lookup when calling
global functions in local scope, which also means dodging the
autoloader. The caveat is that calling local functions in local scope
triggers the autoloader on first encounter, but at least it can be
marked as undeclared in the symbol table once, instead of in every
namespace, which also means triggering the autoloader only once.

Ilija

---

Next page: https://externals.io/message/124718.md?page=2
