Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Performance increasement by integer priority #17

Merged
merged 5 commits into from
Nov 6, 2016
Merged

Performance increasement by integer priority #17

merged 5 commits into from
Nov 6, 2016

Conversation

marc-mabe
Copy link
Member

After my PR #16 I experimented with integer priorities.

It's no longer storing priorities as string with .0 suffix but store as integer. This makes it harder to merge listeners of the same priority by name, wildcard or shared but in the end I sow a very good performance improvement, only MultipleEventMultipleSharedListener decreases a little (see below).

All tests passed for me but it should be tested carefully anyway ;)

PHP 5.6

PHP 5.6.14-0+deb8u1 (cli) (built: Oct 4 2015 16:13:10)
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2015 Zend Technologies
with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2015, by Zend Technologies

Branch: develop

ZendBench\EventManager\MultipleEventIndividualSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000237437248] [42,116.39107]


ZendBench\EventManager\MultipleEventLocalListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000196894646] [50,788.58272]


ZendBench\EventManager\MultipleEventMultipleLocalAndSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0002710880280] [3,688.83867]


ZendBench\EventManager\MultipleEventMultipleSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000575615883] [17,372.69644]


ZendBench\EventManager\SingleEventMultipleListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000446063042] [22,418.35585]


ZendBench\EventManager\SingleEventMultipleSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000478469849] [20,899.95854]


ZendBench\EventManager\SingleEventSingleListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000078002930] [128,200.31299]


ZendBench\EventManager\SingleEventSingleSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000106687546] [93,731.65281]

Branch: integer_priority

ZendBench\EventManager\MultipleEventIndividualSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000230637074] [43,358.16375]


ZendBench\EventManager\MultipleEventLocalListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000166062355] [60,218.34387]


ZendBench\EventManager\MultipleEventMultipleLocalAndSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0002452962399] [4,076.70334]


ZendBench\EventManager\MultipleEventMultipleSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000640497684] [15,612.85894]


ZendBench\EventManager\SingleEventMultipleListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000399168015] [25,052.10747]


ZendBench\EventManager\SingleEventMultipleSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000434321880] [23,024.39838]


ZendBench\EventManager\SingleEventSingleListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000066055775] [151,387.21856]


ZendBench\EventManager\SingleEventSingleSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000096778870] [103,328.34056]

PHP 7.0

PHP 7.0.0 (cli) (built: Dec 5 2015 23:50:11) ( NTS )
Copyright (c) 1997-2015 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2015 Zend Technologies

Fatal error: Uncaught TypeError: Argument 1 passed to Athletic\Common\CmdLineErrorHandler::handleException() must be an instance of Exception, instance of Error given in /home/mabe/workspace/zend-eventmanager/vendor/athletic/athletic/src/Athletic/Common/CmdLineErrorHandler.php:35
Stack trace:
#0 [internal function]: Athletic\Common\CmdLineErrorHandler->handleException(Object(Error))
#1 {main}
  thrown in /home/mabe/workspace/zend-eventmanager/vendor/athletic/athletic/src/Athletic/Common/CmdLineErrorHandler.php on line 35

Something with athletic doesn't work with PHP-7 but I have no time to figure that out.

@Ocramius
Copy link
Member

Ocramius commented Dec 6, 2015

What about the registering events stuff? You increased th executed function count by quite a lot

@marc-mabe
Copy link
Member Author

The general logic for registering events stuff (attach / detach) hasn't changed.
Only the executed function calls of triggering an event has been changed as follows:

  • only "direct" events attached, no wildcard events - no shared events = one less function call
  • else it increases calls as many functions as following events of (wildcard / shared) with the same priority already registered minus 1

I'll create some benchmarks affecting this and a benchmark with the event structure of the skeleton application as real world scenario. (Or do you have a better real world application where I can get the event structure from?) Including PHP 7 bench as I think the overhead of a build-in function call has been improved a lot.

Marc

}
if ($this->sharedManager) {
foreach ($this->sharedManager->getListeners($this->identifiers, $eventName) as $p => $l) {
$listeners[$p] = isset($listeners[$p]) ? array_merge($listeners[$p], $l) : $l;
Copy link
Member

Choose a reason for hiding this comment

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

array_merge() is called repeatedly now

@Ocramius
Copy link
Member

Ocramius commented Dec 8, 2015

@marc-mabe
Copy link
Member Author

Yes it is but only for wildcard and shared events if there are already events attached with the same priority.

@marc-mabe
Copy link
Member Author

@Ocramius I restructured the internal array of listeners generated. This looks a bit strange but it no longer merges the list of listeners into one list but instead create a new small list per list where the existing lists get pushed to. This way it reduces the internal iterations of each list and also reduces the amount of memory allocated for the merged list which improves performance ;)

I created an own benchmark script that shows a big speed-up
(https://gist.github.com/marc-mabe/607bc15ca4394861824c)

bench_em.php

php=php
$php -v && echo "#### integer_priority" && sleep 1 && $php bench-em.php './zend-eventmanager' && echo "#### develop" && sleep 1 && $php bench-em.php './zend-eventmanager-develop'
PHP 5.6.14-0+deb8u1 (cli) (built: Oct  4 2015 16:13:10) 
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2015 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2015, by Zend Technologies
#### integer_priority
  100 listerners:          1.217365026474ms      mem: 863088b    peak mem: 986176b
+ 100 * listerners:        2.4768431186676ms     mem: 1063304b   peak mem: 1286232b
+ 100 shared listerners:   3.7995231151581ms     mem: 1262336b   peak mem: 1584064b
+ 100 shared * listerners: 5.8322820663452ms     mem: 1461608b   peak mem: 2102240b
#### develop
  100 listerners:          1.3990108966827ms     mem: 864880b    peak mem: 1088408b
+ 100 * listerners:        3.3812639713287ms     mem: 1062104b   peak mem: 1660640b
+ 100 shared listerners:   5.6335699558258ms     mem: 1261256b   peak mem: 2124192b
+ 100 shared * listerners: 7.7049930095673ms     mem: 1459928b   peak mem: 2516072b
php="./php-src/sapi/cli/php"
$php -v && echo "#### integer_priority" && sleep 1 && $php bench-em.php './zend-eventmanager' && echo "#### develop" && sleep 1 && $php bench-em.php './zend-eventmanager-develop'
PHP 7.0.0 (cli) (built: Dec  5 2015 23:50:11) ( NTS )
Copyright (c) 1997-2015 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2015 Zend Technologies
#### integer_priority
  100 listerners:          0.66661286354065ms    mem: 835920b    peak mem: 876968b
+ 100 * listerners:        1.3286390304565ms     mem: 922856b    peak mem: 1007664b
+ 100 shared listerners:   2.0087690353394ms     mem: 1007368b   peak mem: 1132176b
+ 100 shared * listerners: 2.7087361812592ms     mem: 1090544b   peak mem: 1378792b
#### develop
  100 listerners:          0.71399188041687ms    mem: 836496b    peak mem: 913744b
+ 100 * listerners:        1.4121708869934ms     mem: 919992b    peak mem: 1114152b
+ 100 shared listerners:   2.2388920783997ms     mem: 1004824b   peak mem: 1346440b
+ 100 shared * listerners: 2.9184379577637ms     mem: 1088320b   peak mem: 1429936b

Athletic

$ git checkout integer_priority
Switched to branch 'integer_priority'
$ vendor/bin/athletic -p benchmarks

ZendBench\EventManager\MultipleEventIndividualSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000230810165] [43,325.64808]


ZendBench\EventManager\MultipleEventLocalListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000162132740] [61,677.85728]


ZendBench\EventManager\MultipleEventMultipleLocalAndSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0002359691620] [4,237.84189]


ZendBench\EventManager\MultipleEventMultipleSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000623136997] [16,047.83546]


ZendBench\EventManager\SingleEventMultipleListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000351785183] [28,426.43887]


ZendBench\EventManager\SingleEventMultipleSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000397550106] [25,154.06196]


ZendBench\EventManager\SingleEventSingleListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000062540054] [159,897.52661]


ZendBench\EventManager\SingleEventSingleSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000096153259] [104,000.63477]
$ git checkout develop
Switched to branch 'develop'
Your branch is up-to-date with 'zf/develop'.
$ vendor/bin/athletic -p benchmarks

ZendBench\EventManager\MultipleEventIndividualSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000244742393] [40,859.28824]


ZendBench\EventManager\MultipleEventLocalListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000201146126] [49,715.10120]


ZendBench\EventManager\MultipleEventMultipleLocalAndSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0002657647610] [3,762.72609]


ZendBench\EventManager\MultipleEventMultipleSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000565294743] [17,689.88679]


ZendBench\EventManager\SingleEventMultipleListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000426486969] [23,447.37525]


ZendBench\EventManager\SingleEventMultipleSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000463457108] [21,576.96977]


ZendBench\EventManager\SingleEventSingleListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000080451012] [124,299.24489]


ZendBench\EventManager\SingleEventSingleSharedListener
    Method Name   Iterations    Average Time      Ops/second
    -------  ------------  --------------    -------------
    trigger: [5,000     ] [0.0000108434200] [92,221.82645]

@@ -27,7 +27,7 @@ public function setUp()

public function getListeners(SharedEventManager $manager, array $identifiers, $event, $priority = 1)
{
$priority = (int) $priority . '.0';
$priority = (int) $priority;
Copy link
Member

Choose a reason for hiding this comment

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

Note to self: this will affect every single component we've upgraded that implements a "getListeners" method of some sort for introspecting attached listeners.

Copy link
Member

Choose a reason for hiding this comment

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

Which may be a rationale for re-instating the getListeners() method. That said, it could be marked private, and libraries could use reflection in order to query it.

This is me brainstorming via github comments. Sue me.

@Ocramius
Copy link
Member

@marc-mabe one last thing for this PR: can we move the benchmarks into athletic benchmarks (as part of the zf2 suite)?

@weierophinney do we have any tool to store the results over time, so that we don't degrade performance later on?

@Ocramius Ocramius added this to the 3.0.0 milestone Dec 15, 2015
@Ocramius Ocramius self-assigned this Dec 15, 2015
@gianarb
Copy link

gianarb commented Dec 15, 2015

@Ocramius I don't know if exists a tool to manage and check benchmark, it is a requirements for zend framework?

  • TestSuite green
  • Code PSR-2
  • Benchmark does not decrease?
  • Coverage does not decrease maybe.. 👍

@weierophinney
Copy link
Member

@Ocramius we don't currently have a way to store the results; at this time, I've been looking through past PRs that do performance changes to see what happens.

We could potentially cache the results on Travis; the problem will be writing a script that does a diff between the cache and the current run.

@Ocramius
Copy link
Member

@weierophinney that seems to be a bit fragile - travis clears the files and boom. What about pushing the states to a secondary repo via an encrypted SSH key?

@marc-mabe
Copy link
Member Author

@Ocramius @weierophinney It would be great to have a benchmark monitoring system but it would be required to run the same benchmark in exactly the same environment (same HW resources + same software + no concurrent running jobs) on different software versions (last commit hashes and properly last release). This makes it hard running benchmarks automatically and monitor them.

And Travis doesn't match this requirements.

@marc-mabe
Copy link
Member Author

@Ocramius Sure, I can add this small bench script to athletic but there are already benchmarks for it - the main reason for writing my own was compatibility with PHP-7.0 and to have a better comparison between both versions incl. memory consumption.

@Ocramius
Copy link
Member

Ocramius commented Jan 5, 2016

@marc-mabe the performance tracking discussion is something the @phpbench folks are working on via phpbench/phpbench#252.

@weierophinney
Copy link
Member

@marc-mabe Does this supercede #16?

@weierophinney
Copy link
Member

@marc-mabe Nevermind, answered my own question; it does.

@weierophinney
Copy link
Member

I've just performed benchmarks across each of PHP 5.5, 5.6, and 7, on both the current develop branch and a version merging this patch. Here's what I'm seeing, after re-running the benchmarks several times to see general trends:

  • MultipleEventMultipleSharedListener is slower in the patched version across all PHP versions (by ~10%).

All others (typically) trend faster with the patch applied.

I'm really not sure what to do here. While the general trend is faster, the specific metric I'm most interested in is the one listed above, as that's the one in play when using zend-mvc, and thus the metric that will most affect users.

@ezimuel, @bakura10, @Ocramius — thoughts?

@bakura10
Copy link
Contributor

By how much percent all the other uses cases are faster? I suppose the MultipleEventMultipleSharedListener is slower because of the multiple foreach instead of array_merge_recursive, right?

@weierophinney
Copy link
Member

@bakura10 I didn't profile to see exactly where the slowdown happens, only which scenario is slower. :)

The other scenarios were typically in the 5% faster range, with some as high as 15-20%.

@Ocramius
Copy link
Member

@weierophinney MultipleEventMultipleSharedListener looks like an edge-case to me: I'd say that we can go ahead here

@bakura10
Copy link
Contributor

Me too. From @marc-mabe it seems the increase in all other scenarios is subtanstial enough to cope this highly specific use case.

@weierophinney
Copy link
Member

@Ocramius — um, what? As I noted, MultipleEventMultipleSharedListener is the exact scenario present in zend-mvc; that's not an edge case, that's the primary use-case. As such, the changes in this patch undo some of the performance gains @bakura10 and @ezimuel worked so hard on…

@Ocramius
Copy link
Member

@weierophinney does the MVC have multiple shared listeners? O_o

@weierophinney
Copy link
Member

@Ocramius Typically, yes. Most modules I've reviewed will:

  • Attach one or more listeners directly on the MVC event manager instance, AND
  • Attach one or more shared listeners for events triggered by controllers, service layers, and/or domain logic.

We even do so in quite a number of Apigility modules, and the above has been a recommended best practice for module development made in our tutorials, in the training courses, and even the certification exam.

@marc-mabe
Copy link
Member Author

Let me try if I can improve this case to get at least the same performance as before.

@marc-mabe
Copy link
Member Author

@weierophinney @Ocramius ping

@Ocramius Ocramius modified the milestones: 3.0.0, 3.1.0 Jun 30, 2016
@Ocramius
Copy link
Member

Moved to 3.1.0

// If the event was asked to stop propagating, do so
if ($event->propagationIsStopped()) {
$responses->setStopped(true);
break 3;
Copy link
Member

Choose a reason for hiding this comment

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

This is basically return $responses, no?

Copy link
Member Author

Choose a reason for hiding this comment

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

You are right

@Ocramius
Copy link
Member

@marc-mabe a couple more things (sorry in advance):

  • we may drop 5.5 support, allowing usage of the splat operator will make things even faster
  • some of the naming in the patch really needs adjustment
  • a bit worried about all the additional looping going on in here... hard to debug and hard to read. Are there sections that can safely be moved to private methods, without killing performance?

@marc-mabe
Copy link
Member Author

@Ocramius thanks for reviewing this!
I'll go for finding better namings and replacing the 'break 3' statements in favor of return statements.
Mooving some loops into a function I'm currently unsure how this could look like but I'll see what I can do.

@Ocramius
Copy link
Member

I'm currently unsure how this could look like but I'll see what I can do.

Not sure either. If it has a visible impact, then I guess it's fine as-is, but the complexity of the code is indeed huge already...

@marc-mabe
Copy link
Member Author

@Ocramius I have renamed all variables and improved coding style a little.

Also did one or two micro optimizations committed to this branch :)
-> Additionally I found several other possible optimizations but they all result in (small) different behavior / BC break but I split them out so this PR contains backward compatible changes.

Benchmarks

PHP 5.6.24-0+deb8u1

Name develop (Ops/s) integer_priority (Ops/s) %
MultipleEventIndividualSharedListener 16,514.00912 15,012.30891 -9
MultipleEventLocalListener 19,828.14958 27,620.81750 +39
MultipleEventMultipleLocalAndSharedListener 1,215.84855 1,360.70463 +12
MultipleEventMultipleSharedListener 6,331.88891 6,553.44231 +3
SingleEventMultipleListener 6,336.17610 7,125.86727 +12
SingleEventMultipleSharedListener 5,893.02007 6,188.57283 +5
SingleEventSingleListener 49,023.96320 71,316.41859 +45
SingleEventSingleSharedListener 37,440.18396 33,340.78425 -11

PHP 7.0.9

Name develop (Ops/s) integer_priority (Ops/s) %
MultipleEventIndividualSharedListener 106,749.19575 109,851.44677 +3
MultipleEventLocalListener 136,832.65477 183,574.22969 +34
MultipleEventMultipleLocalAndSharedListener 10,766.38032 12,686.76842 +18
MultipleEventMultipleSharedListener 48,424.12487 53,234.50422 +10
SingleEventMultipleListener 64,647.50091 79,483.94139 +23
SingleEventMultipleSharedListener 59,821.03483 64,669.82848 +8
SingleEventSingleListener 359,279.77934 480,700.48365 +34
SingleEventSingleSharedListener 254,086.28857 242,087.08501 -5

@marc-mabe
Copy link
Member Author

@Ocramius @weierophinney Can this PR be merged or still is something missing?

@Ocramius
Copy link
Member

Ocramius commented Nov 6, 2016

Looks good to me, but the code is indeed harder to read. @weierophinney I think this is OK to merge, given the improvements.

@Ocramius Ocramius merged commit 1a25a47 into zendframework:develop Nov 6, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants