[PHP-DEV] [Concept] Flip relative function lookup order (global, then local)

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
(Benchmark for flip-ns-call-scope-lookup-order · GitHub).
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] Implement frameless internal function calls by iluuu1994 · Pull Request #12461 · php/php-src · GitHub
[2] Implement INIT_FCALL offset optimization by iluuu1994 · Pull Request #13634 · php/php-src · GitHub
[3] Change the way built-in functions are resolved · Issue #13632 · php/php-src · GitHub
[4] [RFC] Flip ns call scope lookup order by iluuu1994 · Pull Request #14529 · php/php-src · GitHub
[5] symfony/src/Symfony/Bridge/PhpUnit/ClockMock.php at 7.1 · symfony/symfony · GitHub
[6] Shadowed internal functions in the top 1000 Composer packages · GitHub

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

On Fri, Aug 2, 2024, at 18:51, Ilija Tovilo wrote:

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

On Fri, 2024-08-02 at 18:51 +0200, 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].

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.

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

On 2 August 2024 18:19:41 BST, Nick Lockheart <lists@ageofdream.com> wrote:

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]

On Fri, 2024-08-02 at 18:53 +0100, Rowan Tommins [IMSoP] wrote:

On 2 August 2024 18:19:41 BST, Nick Lockheart <lists@ageofdream.com>
wrote:
> 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]

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.

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

On Fri, 2024-08-02 at 18:53 +0100, Rowan Tommins [IMSoP] 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.

Sorry, I forgot the link: PHP: rfc:use_global_elements

On 2 August 2024 19:46:03 BST, Nick Lockheart <lists@ageofdream.com> wrote:

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 <[VOTE] declare(function_and_const_lookup='global') - Externals; 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]

Hi Ilija,

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

On 02/08/2024 17:51, Ilija Tovilo wrote:

* 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

On Fri, 2024-08-02 at 21:37 +0100, Bilge wrote:

Hi Ilija,
I think this proposal has legs, and you are right to rekindle it,
instead of letting it die quietly.
On 02/08/2024 17:51, Ilija Tovilo wrote:

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

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.

On 03.08.2024 at 00:00, Nick Lockheart wrote:

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&gt;
[2] <PECL :: Package :: runkit7;
[3] <Issues · krakjoe/uopz · GitHub;
[4] <Issues · zenovich/runkit · GitHub;

Cheers,
Christoph

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

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

On 04.08.2024 at 08:19, Nick Lockheart wrote:

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

Hi Rob

On Fri, Aug 2, 2024 at 7:10 PM Rob Landers <rob@bottled.codes> wrote:

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

Hi Claude

On Fri, Aug 2, 2024 at 9:02 PM Claude Pache <claude.pache@gmail.com> wrote:

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.: Online PHP editor | output for 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

On Sun, 2024-08-04 at 19:53 +0200, Ilija Tovilo wrote:

Hi Claude

On Fri, Aug 2, 2024 at 9:02 PM Claude Pache <claude.pache@gmail.com>
wrote:
>
> 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.: Online PHP editor | output for 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

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.

On Fri, 2 Aug 2024, Ilija Tovilo wrote:

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

On Mon, Aug 5, 2024 at 1:23 PM Derick Rethans <derick@php.net> wrote:

On Fri, 2 Aug 2024, Ilija Tovilo wrote:

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

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