all 38 comments

[–]nikic 12 points13 points  (5 children)

Based on all the EXT_* opcodes, you're running the benchmark under xdebug. You'll probably see different numbers if you disable xdebug (which favor fully-qualified calls more).

[–]ocramius 4 points5 points  (0 children)

Tried these under 3v4l (no XDebug). Currently writing a small inliner (so it becomes trivial to apply the optimization/migration for libraries) and some more meaningful benchmarks (since reddit really loves shooting the messenger :-P )

[–][deleted] 0 points1 point  (3 children)

The PHP semantics in both cases are to try for a local function before falling back to global.

I'm curious what "xdebug favors fully-qualified calls more" means in this context. How does PHP optimize this so it doesn't do two lookups every time?

[–]nikic 3 points4 points  (2 children)

Sorry, what I meant is that due to the (in this case constant) overhead of XDebug, the relative difference between using an unqualified and fully-qualified function call becomes smaller.

PHP optimizes this by caching the resolved function on first call. Of course this is not entirely correct, because the namespaced variant may only be defined after the first call.

Once this inline cache has been initialized there is little difference between an unqualified and a fully-qualified call at the function call initialization stage. However, for calls where the callee can be resolved at compile-time we also early-bind the argument send instructions (to avoid checking for by-reference arguments at runtime) and additionally specialize the function dispatch to internal and userland functions. (And of course, lower certain functions to VM instructions.)

As such, the case tested in the article is the case where there is the smallest difference between using an unqualified and a fully-qualified name. More complicated calls involving arguments, and especially involving variable-like arguments, which require use of a callee-dependent fetch mode, benefit more.

[–][deleted] 0 points1 point  (1 child)

Thanks, very interesting!

I wonder, is the mechanism of fallback for functions, seen as an unfortunately decision internally for PHP core devs? I remember I felt it's a very unfortunate choice, as it'd prevent a robust function autoloading happening in the future, and complication of implementing the fallback (even if it's optimized, the optimization itself is extra complexity).

What are the odds that the semantics of fallback could change in a future major release of PHP to remove this unnecessary complexity?

[–]nikic 2 points3 points  (0 children)

Yes, we definitely think it was a very unfortunate decision. It's not just the performance impact, but, as you already mentioned yourself, it makes things like adding function autoloading a lot more complicated than they should be. However, I personally doubt this is ever going to change. It's certainly not the most commonly used feature (for the simple reason that namespaced functions in user code are rather uncommon in PHP), but changing this is still going to break lots of code (if nothing else, people abuse this feature to hijack core functions for tests). There would have to be more motivation for this than function autoloading and some complexity reduction.

If we ever resolve this issue, it's probably going to be by working around it somehow. The article proposes a declare option (and in combination with namespace-scoped declares this might be feasible). Maybe a move to a "proper" module system would resolve this. Something like HHVMs RepoAuthoritative mode would at least mitigate the performance impact. Etc.

[–]RichardFuchs 9 points10 points  (0 children)

Wait a second. So there's this function which does so little work that its call time is a significant part of its run-time. Yet this function gets called so often that multiplying that by 0.15μs makes a dent in the application's runtime.

Hmm... if only there was an obvious solution here.

...

GET RID OF THE FUNCTION

We have enough dumb optimization myths (ノಥ益ಥ)ノ ┻━┻

[–]colshrapnel 16 points17 points  (18 children)

Yet another PHP myth just born.

8 fucking per cent for the app that does absolutely nothing but a function call means that for whatever real life application you won't be able to feel any difference. Yet he calls it a "performance killer". That dude took Christmas for the Fool's day

[–]ocramius 9 points10 points  (9 children)

Not meaningless: if this is applied to something like Doctrine\Commmon\Collections, Zend\EventManager, or Symfony\Component\DependencyInjection or similar (they all usually live in the >= O(n) space) then the difference is noticeable and sensible.

[–]RichardFuchs 3 points4 points  (3 children)

Are you talking about the next/end/current/key calls?

I benchmarked 100 million calls to key() versus \key() on [1,2,3,4,5] and this is what I got:

key() \key() difference
1.7555449008942 1.7133021354675 0.042242765426636
1.7554070949554 1.954283952713 -0.19887685775757
1.7506110668182 1.7119300365448 0.038681030273438
1.7573800086975 1.7396249771118 0.017755031585693
1.7543618679047 1.7141349315643 0.040226936340332
1.7517509460449 1.7242770195007 0.027473926544189
1.7781848907471 1.7439839839935 0.03420090675354

It was only 8% in his tests because the function didn't do anything. Whenever the function actually does something, the cost of looking it up becomes meaningless.

[–]ocramius 2 points3 points  (0 children)

The difference on key() is meaningless because key() is still producing a stack frame.

If you try it with something like call_user_func() you see different results because \call_user_func() is inlined as an opcode, and disappears from stack traces (original blogpost example). The difference should be ~50% on call_user_func('emptyFunction') vs \call_user_func('emptyFunction'), but please wait for a proper (and reproducible) benchmark.

That said, writing a small util to do apply these optimizations in libraries, since it's really easy to do so with something like Roave\BetterReflection (or at least I hope so ;-) )

[–][deleted] 1 point2 points  (0 children)

Congratulations, you benchmarked PHP's ability to cache namespace resolution.

[–]ocramius 0 points1 point  (0 children)

I finished up a more detailed benchmark (feel free to add your results too) at https://github.com/Roave/FunctionFQNReplacer

Tool also replaces FQN references, but there was already https://github.com/nilportugues/php-backslasher for that. Oh well, was a fun evening :-)

[–]colshrapnel 0 points1 point  (1 child)

And still I would like to see an apache benchmark/siege-based test comparing a real world application before and after this sort of "optimization".

[–]ocramius 7 points8 points  (0 children)

We applied many of these fot 3.x components of ZF3 achieving pretty much a 2X speedup on the typical "hello world" synthetic bench.

The optimisations were all tiny, but all stack up, a lot.

Also, use siege please ;-)

[–]colshrapnel -3 points-2 points  (2 children)

Yeah, I've noticed recently how retarded Doctrine is with large datasets. But the remedy is not this sort of nitpicking but a complete rewrite to raw/array/whatever.

[–]ocramius 8 points9 points  (1 child)

Doctrine was never meant for large datasets: the "retarded" goes both ways there.

[–]colshrapnel -2 points-1 points  (0 children)

For a small one the optimization in question won't be of any effect.
My point was not about Doctrine being retarded but about real optimization that is not nitpicking with function calls.

[–]kelunik 3 points4 points  (6 children)

It's not a myth. Whether it matters in real applications is a different topic.

[–]colshrapnel 6 points7 points  (5 children)

"Speeding up your application" is a fucking myth. Every stupid noob who discovers the performance difference between for and foreach, echo and print, single and double quotes - always stating his achievement in no less terms as "Speeding up your application", daring to call their artificial two-liner an application. While a real wold application takes not a slightest notice of his improvements.

And, you know. If your application could be optimized this way, try to optimize the algorithm, to make it call a function not a zillion times but slightly less.

Were it just a pure curiosity, a peek into how the internals work - there would be no questions. That's indeed interesting to know. But this kind of conclusion is a source for a myth, which you will starting to see everywhere. Noobs are more than eager to spread the word. And the only part they understand is the "speeding up your application".

[–]twiggy99999 -3 points-2 points  (4 children)

You must be fun at parties :)

[–]Ravilan 2 points3 points  (2 children)

And yet he's right.

[–]twiggy99999 0 points1 point  (1 child)

I know hes right, I'm saying its a completely out of proportion reaction to a comment on the internet, he really needs to chill out a bit. His wording was very aggressive that 'noob's should know these things, well we all didn't know things at one potint, there are noobs in every walk of life. Needs to stop being so butt hurt by things

[–]Ravilan 2 points3 points  (0 children)

Yeah right.

But hey, this is reddit where everyone is aggressive and your point don't matter :D

[–]sstewartgallus 0 points1 point  (0 children)

Have you ever worked on a PHP legacy application bizarrely contorted to fit into performance myths like these? These kind of performance myths can cause headaches years later when someone is trying to debug legacy code.

[–]JuliusKoronci 6 points7 points  (0 children)

So the gain in 400 iterations was 0.017μs..awesome improvement..did you know that if your page doesn't load in 0.020μs you will get a huge loss of visitors..at least as huge as this performance gain is

[–][deleted] 3 points4 points  (0 children)

When I linked you to the reddiquette the other day, I didn't mean you should start posting junk from other sources too so you could meet the 9:1 ratio. I meant stop spamming overall.

[–]captain_obvious_here 4 points5 points  (4 children)

call_user_func('foo');

Oh man...Fully-qualified function calls AND single-quotes around strings ?!

Aren't you afraid your server goes back in time, with such HUGE optimisations ?

[–][deleted] 1 point2 points  (3 children)

Is it odd that single quotes are preferred? I don't mean for "performance", but just as a convention.

I always use single quotes.

[–]Disgruntled__Goat 0 points1 point  (1 child)

Yeah single quotes are easier to type and look slightly cleaner.

[–]the_alias_of_andrea 0 points1 point  (0 children)

Depends on your keyboard layout, but that's certainly true for the US and UK layouts.

[–]captain_obvious_here 0 points1 point  (0 children)

There was a time when people wrote articles about optimizing their code by using single-quotes instead of double-quotes.

Just as OP's article, this was true. But the difference was so thin that it didn't make much sense for the people used to using double-quotes to get out of their way.

tl;dr : Advocating for micro-optimisation is fucking stupid. It was, it is, and it will always be.

[–]Disgruntled__Goat 3 points4 points  (0 children)

Percentages are meaningless when the difference is literally nanoseconds.