Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[EventDispatcher][VarDumper] optimize perf by leveraging Closure::fromCallable() #29245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 10, 2018

Conversation

nicolas-grekas
Copy link
Member

Q A
Branch? master
Bug fix? no
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets -
License MIT
Doc PR -

Callables are notably slower than closures. Let's turn then to closures thanks to Closure::fromCallable().

This doesn't affect performance for run-once listeners.
And improves performance for events that are dispatched several times.

Same for VarDumper's casters.

Copy link
Contributor

@ostrolucky ostrolucky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not incorporated inside sortListeners function? Instead it repeats the logic but ignores sort marking and is used only during dispatching

This doesn't affect performance for run-once listeners.

How come? It makes extra operations (new closure, Closure::fromCallable call)

src/Symfony/Component/EventDispatcher/EventDispatcher.php Outdated Show resolved Hide resolved
src/Symfony/Component/EventDispatcher/EventDispatcher.php Outdated Show resolved Hide resolved
src/Symfony/Component/EventDispatcher/EventDispatcher.php Outdated Show resolved Hide resolved
@nicolas-grekas
Copy link
Member Author

@ostrolucky thanks for the review!

Why is this not incorporated inside sortListeners function? Instead it repeats the logic but ignores sort marking and is used only during dispatching

Because sortListeners has public side effect: it drive the return value of getListeners().
Changing would mean breaking BC.

How come? It makes extra operations (new closure, Closure::fromCallable call)

and removes some. I did benchmark this, there is no measurable difference.

@ostrolucky
Copy link
Contributor

it drive the return value of getListeners()

getListeners() should continue returning the exact listeners that where added

I didn't mean to to imply you should save the optimization result into sorted property, you can still put it to optimized, but from inside sortListeners. That wouldn't change anything on public side.

@nicolas-grekas
Copy link
Member Author

you can still put it to optimized, but from inside sortListeners

that could defeat the extra laziness added here (not instantiating lazy listeners when propagation is stopped before them)

@nicolas-grekas nicolas-grekas force-pushed the callable-optim branch 2 times, most recently from 5a9ac14 to 5bc59f1 Compare November 18, 2018 10:29
@ostrolucky
Copy link
Contributor

that could defeat the extra laziness added here (not instantiating lazy listeners when propagation is stopped before them)

I don't see how would it defeat it 🤔 Only way that closure is replaced with delegated one is when it's executed, which will happen only during dispatching.

@nicolas-grekas
Copy link
Member Author

Because of the $listener[0] = $listener[0](); line, which is the one that is made lazy here.

@ostrolucky
Copy link
Contributor

I get that, but that line is executed only when outer closure is executed. I don't see why would moving construction of this closure to different place change when is it executed

@nicolas-grekas
Copy link
Member Author

Because we have to call that line to populate the sortedListeners property.

@ostrolucky
Copy link
Contributor

ostrolucky commented Nov 19, 2018

This closure trick increases speed by 0.6μs aggregated (8%). 0.15μs from that is just thanks to inlining getListeners call. So closure trick alone is 0.45μs. Measured with phpbench, 100 listeners attached (and all of them executed). Correlation on number of listeners is very linear.

I don't think this is worth increased complexity and number of untested paths. Also, is there a PHP bug report? I think this is something better to be improved on language level.

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Nov 20, 2018

We made current Symfony versions really fast because we made such changes. Small improvements for your bench can be a significant one for a specific use case. And it's already been proven that many small improvements aggregate to significant general ones.

The real issue is having EventDispatcher mutable. We should inspect existing use cases where this is needed (if any) and consider deprecating if possible IMHO. But that shouldn't block this one.

@ostrolucky
Copy link
Contributor

Well if we decide it's worth it, can you at least properly comment this code, cover it by tests and notify upstream about workarounds we need to do to increase performance so there is at least some chance we can eventually remove them? This was proven successful with eg inlining constants in DI Container. Performance tweaks like this are killing readability and hence ability to debug and contribute a lot. As you can see from my questions there are lot of non-obvious things for other people.

I still don't know in what case would if ($listener[0] instanceof \Closure) { condition be needed, because it's not covered by tests. And there is no longer even coverage for calling getListeners() call from dispatch().

@stof
Copy link
Member

stof commented Nov 20, 2018

I still don't know in what case would if ($listener[0] instanceof \Closure) { condition be needed, because it's not covered by tests.

that part is the lazy-loading of the listener object.

@ostrolucky
Copy link
Contributor

I see that, I just don't see how can be listener modified outside of this proxy closure. But testcase would help me with that wink wink

@nicolas-grekas nicolas-grekas force-pushed the callable-optim branch 3 times, most recently from 446270e to 68f3b51 Compare November 21, 2018 14:31
@nicolas-grekas
Copy link
Member Author

The discussed code path is now tested @ostrolucky, testMutatingWhilePropagationIsStopped.

Don't be fooled by the diff stat, I just renamed AbstractEventDispatcherTest to EventDispatcherTest and added this test case.

@ostrolucky
Copy link
Contributor

Thanks. I can see now that same closure is evaluated by sortListeners and optimizeListeners might not re-triggered after that happens.

fabpot added a commit that referenced this pull request Dec 10, 2018
…ariables (ostrolucky)

This PR was merged into the 4.1 branch.

Discussion
----------

Optimize perf by replacing call_user_func with dynamic variables

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

This provides similar boost as in #29245, but on more places and without complexity increase. Check eg. https://github.com/fab2s/call_user_func for proof

Fabpot failure unrelated

Commits
-------

0c6ef01 Optimize perf by replacing call_user_func with dynamic vars
symfony-splitter pushed a commit to symfony/cache that referenced this pull request Dec 10, 2018
…ariables (ostrolucky)

This PR was merged into the 4.1 branch.

Discussion
----------

Optimize perf by replacing call_user_func with dynamic variables

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

This provides similar boost as in symfony/symfony#29245, but on more places and without complexity increase. Check eg. https://github.com/fab2s/call_user_func for proof

Fabpot failure unrelated

Commits
-------

0c6ef01713 Optimize perf by replacing call_user_func with dynamic vars
symfony-splitter pushed a commit to symfony/validator that referenced this pull request Dec 10, 2018
…ariables (ostrolucky)

This PR was merged into the 4.1 branch.

Discussion
----------

Optimize perf by replacing call_user_func with dynamic variables

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

This provides similar boost as in symfony/symfony#29245, but on more places and without complexity increase. Check eg. https://github.com/fab2s/call_user_func for proof

Fabpot failure unrelated

Commits
-------

0c6ef01713 Optimize perf by replacing call_user_func with dynamic vars
symfony-splitter pushed a commit to symfony/serializer that referenced this pull request Dec 10, 2018
…ariables (ostrolucky)

This PR was merged into the 4.1 branch.

Discussion
----------

Optimize perf by replacing call_user_func with dynamic variables

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

This provides similar boost as in symfony/symfony#29245, but on more places and without complexity increase. Check eg. https://github.com/fab2s/call_user_func for proof

Fabpot failure unrelated

Commits
-------

0c6ef01713 Optimize perf by replacing call_user_func with dynamic vars
symfony-splitter pushed a commit to symfony/form that referenced this pull request Dec 10, 2018
…ariables (ostrolucky)

This PR was merged into the 4.1 branch.

Discussion
----------

Optimize perf by replacing call_user_func with dynamic variables

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

This provides similar boost as in symfony/symfony#29245, but on more places and without complexity increase. Check eg. https://github.com/fab2s/call_user_func for proof

Fabpot failure unrelated

Commits
-------

0c6ef01713 Optimize perf by replacing call_user_func with dynamic vars
symfony-splitter pushed a commit to symfony/http-kernel that referenced this pull request Dec 10, 2018
…ariables (ostrolucky)

This PR was merged into the 4.1 branch.

Discussion
----------

Optimize perf by replacing call_user_func with dynamic variables

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

This provides similar boost as in symfony/symfony#29245, but on more places and without complexity increase. Check eg. https://github.com/fab2s/call_user_func for proof

Fabpot failure unrelated

Commits
-------

0c6ef01 Optimize perf by replacing call_user_func with dynamic vars
@fabpot
Copy link
Member

fabpot commented Dec 10, 2018

Thank you @nicolas-grekas.

@fabpot fabpot merged commit d6a594b into symfony:master Dec 10, 2018
fabpot added a commit that referenced this pull request Dec 10, 2018
… Closure::fromCallable() (nicolas-grekas)

This PR was merged into the 4.3-dev branch.

Discussion
----------

[EventDispatcher][VarDumper] optimize perf by leveraging Closure::fromCallable()

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

Callables are notably slower than closures. Let's turn then to closures thanks to `Closure::fromCallable()`.

This doesn't affect performance for run-once listeners.
And improves performance for events that are dispatched several times.

Same for VarDumper's casters.

Commits
-------

d6a594b [EventDispatcher][VarDumper] optimize perf by leveraging Closure::fromCallable()
@nicolas-grekas nicolas-grekas deleted the callable-optim branch January 25, 2019 14:27
@nicolas-grekas nicolas-grekas modified the milestones: next, 4.3 Apr 30, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.