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

Commit 263a6eb

Browse files
committed
Performance optimization by reusing list of listeners instead of merging into a new array
1 parent 3834843 commit 263a6eb

File tree

3 files changed

+66
-52
lines changed

3 files changed

+66
-52
lines changed

src/EventManager.php

Lines changed: 59 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,9 @@ public function attach($eventName, callable $listener, $priority = 1)
164164
));
165165
}
166166

167-
$this->events[$eventName][(int) $priority][] = $listener;
167+
// see performance note of triggerListeners() why the internal
168+
// event array is structured this way.
169+
$this->events[$eventName][(int) $priority][0][] = $listener;
168170

169171
return $listener;
170172
}
@@ -197,16 +199,16 @@ public function detach(callable $listener, $eventName = null, $force = false)
197199
}
198200

199201
foreach ($this->events[$eventName] as $priority => $listeners) {
200-
foreach ($listeners as $index => $evaluatedListener) {
202+
foreach ($listeners[0] as $index => $evaluatedListener) {
201203
if ($evaluatedListener !== $listener) {
202204
continue;
203205
}
204206

205207
// Found the listener; remove it.
206-
unset($this->events[$eventName][$priority][$index]);
208+
unset($this->events[$eventName][$priority][0][$index]);
207209

208210
// If the queue for the given priority is empty, remove it.
209-
if (empty($this->events[$eventName][$priority])) {
211+
if (empty($this->events[$eventName][$priority][0])) {
210212
unset($this->events[$eventName][$priority]);
211213
break;
212214
}
@@ -262,64 +264,71 @@ protected function triggerListeners(EventInterface $event, callable $callback =
262264
throw new Exception\RuntimeException('Event is missing a name; cannot trigger!');
263265
}
264266

265-
// Initial value of stop propagation flag should be false
266-
$event->stopPropagation(false);
267-
268-
$responses = new ResponseCollection();
269-
270-
foreach ($this->getListenersByEventName($name) as $listener) {
271-
$response = $listener($event);
272-
$responses->push($response);
273-
274-
// If the event was asked to stop propagating, do so
275-
if ($event->propagationIsStopped()) {
276-
$responses->setStopped(true);
277-
break;
278-
}
279-
280-
// If the result causes our validation callback to return true,
281-
// stop propagation
282-
if ($callback && $callback($response)) {
283-
$responses->setStopped(true);
284-
break;
285-
}
286-
}
287-
288-
return $responses;
289-
}
290-
291-
/**
292-
* Get listeners for the currently triggered event.
293-
*
294-
* @param string $eventName
295-
* @return callable[]
296-
*/
297-
private function getListenersByEventName($eventName)
298-
{
299-
$listeners = isset($this->events[$eventName]) ? $this->events[$eventName] : [];
267+
// Merge all listeners by priority together with format
268+
// [
269+
// <int priority1> => [
270+
// [
271+
// [<callable listener1>, <callable listener2>, ...]
272+
// ],
273+
// ...
274+
// ],
275+
// <int priority2> => [
276+
// [
277+
// [<callable listener1>, <callable listener2>, ...]
278+
// ],
279+
// ...
280+
// ],
281+
// ...
282+
// ]
283+
//
284+
// PERFORMANCE NOTE:
285+
// This way pushing the list of listeners into a new generated list
286+
// helps us to not call "array_merge_[recursive]"
287+
// and reusing the list of listeners we already have
288+
// instead of iterating over it and generating a new one.
289+
// -> In result it improves performance by up to 25% even if it looks a bit strange.
290+
$listOfListenersByPriority = isset($this->events[$name]) ? $this->events[$name] : [];
300291
if (isset($this->events['*'])) {
301292
foreach ($this->events['*'] as $p => $l) {
302-
$listeners[$p] = isset($listeners[$p]) ? array_merge($listeners[$p], $l) : $l;
293+
$listOfListenersByPriority[$p][] = $l[0];
303294
}
304295
}
305296
if ($this->sharedManager) {
306-
foreach ($this->sharedManager->getListeners($this->identifiers, $eventName) as $p => $l) {
307-
$listeners[$p] = isset($listeners[$p]) ? array_merge($listeners[$p], $l) : $l;
297+
foreach ($this->sharedManager->getListeners($this->identifiers, $name) as $p => $l) {
298+
$listOfListenersByPriority[$p][] = $l;
308299
}
309300
}
310301

311-
krsort($listeners);
302+
// Sort by priority in reverse order
303+
krsort($listOfListenersByPriority);
312304

313-
$listenersForEvent = [];
305+
// Initial value of stop propagation flag should be false
306+
$event->stopPropagation(false);
314307

315-
foreach ($listeners as $priority => $listenersByPriority) {
316-
foreach ($listenersByPriority as $listener) {
317-
// Performance note: after some testing, it appears that accumulating listeners and sending
318-
// them at the end of the method is FASTER than using generators (ie. yielding)
319-
$listenersForEvent[] = $listener;
308+
// Execute listeners
309+
$responses = new ResponseCollection();
310+
foreach ($listOfListenersByPriority as $listenersList) {
311+
foreach ($listenersList as $listeners) {
312+
foreach ($listeners as $listener) {
313+
$response = $listener($event);
314+
$responses->push($response);
315+
316+
// If the event was asked to stop propagating, do so
317+
if ($event->propagationIsStopped()) {
318+
$responses->setStopped(true);
319+
break 3;
320+
}
321+
322+
// If the result causes our validation callback to return true,
323+
// stop propagation
324+
if ($callback && $callback($response)) {
325+
$responses->setStopped(true);
326+
break 3;
327+
}
328+
}
320329
}
321330
}
322331

323-
return $listenersForEvent;
332+
return $responses;
324333
}
325334
}

src/SharedEventManager.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public function getListeners(array $identifiers, $eventName)
190190
}
191191
}
192192

193-
if (isset($this->identifiers['*']) && ! in_array('*', $identifiers, true)) {
193+
if (isset($this->identifiers['*'])) {
194194
$wildcardIdentifier = $this->identifiers['*'];
195195
if (isset($wildcardIdentifier[$eventName])) {
196196
foreach ($wildcardIdentifier[$eventName] as $p => $l) {

test/EventManagerTest.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ public function getListenersForEvent($event, EventManager $manager)
5656
$r->setAccessible(true);
5757
$events = $r->getValue($manager);
5858

59-
return isset($events[$event]) ? $events[$event] : [];
59+
$listenersByPriority = isset($events[$event]) ? $events[$event] : [];
60+
foreach ($listenersByPriority as $priority => & $listeners) {
61+
$listeners = $listeners[0];
62+
}
63+
64+
return $listenersByPriority;
6065
}
6166

6267
public function testAttachShouldAddListenerToEvent()

0 commit comments

Comments
 (0)