From efd037cd34f4d174d936b3ad764b0ba9d71c6416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 20 Apr 2018 22:21:59 +0200 Subject: [PATCH 1/2] Use sorted array to store timers and remove cancelled ones --- src/Timer/Timers.php | 68 +++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 45 deletions(-) diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index 17bbdac8..813072d4 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -3,8 +3,6 @@ namespace React\EventLoop\Timer; use React\EventLoop\TimerInterface; -use SplObjectStorage; -use SplPriorityQueue; /** * A scheduler implementation that can hold multiple timer instances @@ -17,14 +15,8 @@ final class Timers { private $time; - private $timers; - private $scheduler; - - public function __construct() - { - $this->timers = new SplObjectStorage(); - $this->scheduler = new SplPriorityQueue(); - } + private $timers = array(); + private $schedule = array(); public function updateTime() { @@ -38,36 +30,26 @@ public function getTime() public function add(TimerInterface $timer) { - $interval = $timer->getInterval(); - $scheduledAt = $interval + microtime(true); - - $this->timers->attach($timer, $scheduledAt); - $this->scheduler->insert($timer, -$scheduledAt); + $id = spl_object_hash($timer); + $this->timers[$id] = $timer; + $this->schedule[$id] = $timer->getInterval() + microtime(true); + asort($this->schedule); } public function contains(TimerInterface $timer) { - return $this->timers->contains($timer); + return isset($this->timers[spl_oject_hash($timer)]); } public function cancel(TimerInterface $timer) { - $this->timers->detach($timer); + $id = spl_object_hash($timer); + unset($this->timers[$id], $this->schedule[$id]); } public function getFirst() { - while ($this->scheduler->count()) { - $timer = $this->scheduler->top(); - - if ($this->timers->contains($timer)) { - return $this->timers[$timer]; - } - - $this->scheduler->extract(); - } - - return null; + return reset($this->schedule); } public function isEmpty() @@ -78,31 +60,27 @@ public function isEmpty() public function tick() { $time = $this->updateTime(); - $timers = $this->timers; - $scheduler = $this->scheduler; - - while (!$scheduler->isEmpty()) { - $timer = $scheduler->top(); - if (!isset($timers[$timer])) { - $scheduler->extract(); - $timers->detach($timer); - - continue; + foreach ($this->schedule as $id => $scheduled) { + // schedule is ordered, so loop until first timer that is not scheduled for execution now + if ($scheduled >= $time) { + break; } - if ($timers[$timer] >= $time) { - break; + // skip any timers that are removed while we process the current schedule + if (!isset($this->schedule[$id]) || $this->schedule[$id] !== $scheduled) { + continue; } - $scheduler->extract(); + $timer = $this->timers[$id]; call_user_func($timer->getCallback(), $timer); - if ($timer->isPeriodic() && isset($timers[$timer])) { - $timers[$timer] = $scheduledAt = $timer->getInterval() + $time; - $scheduler->insert($timer, -$scheduledAt); + // re-schedule if this is a periodic timer and it has not been cancelled explicitly already + if ($timer->isPeriodic() && isset($this->timers[$id])) { + $this->schedule[$id] = $timer->getInterval() + $time; + asort($this->schedule); } else { - $timers->detach($timer); + unset($this->timers[$id], $this->schedule[$id]); } } } From be70d8c65f9224ba1765e6761389bf593896114a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 21 Apr 2018 10:11:09 +0200 Subject: [PATCH 2/2] Improve performance by sorting timers only on demand --- src/Timer/Timers.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index 813072d4..7944b4c1 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -17,6 +17,7 @@ final class Timers private $time; private $timers = array(); private $schedule = array(); + private $sorted = true; public function updateTime() { @@ -33,7 +34,7 @@ public function add(TimerInterface $timer) $id = spl_object_hash($timer); $this->timers[$id] = $timer; $this->schedule[$id] = $timer->getInterval() + microtime(true); - asort($this->schedule); + $this->sorted = false; } public function contains(TimerInterface $timer) @@ -49,6 +50,12 @@ public function cancel(TimerInterface $timer) public function getFirst() { + // ensure timers are sorted to simply accessing next (first) one + if (!$this->sorted) { + $this->sorted = true; + asort($this->schedule); + } + return reset($this->schedule); } @@ -59,6 +66,12 @@ public function isEmpty() public function tick() { + // ensure timers are sorted so we can execute in order + if (!$this->sorted) { + $this->sorted = true; + asort($this->schedule); + } + $time = $this->updateTime(); foreach ($this->schedule as $id => $scheduled) { @@ -78,7 +91,7 @@ public function tick() // re-schedule if this is a periodic timer and it has not been cancelled explicitly already if ($timer->isPeriodic() && isset($this->timers[$id])) { $this->schedule[$id] = $timer->getInterval() + $time; - asort($this->schedule); + $this->sorted = false; } else { unset($this->timers[$id], $this->schedule[$id]); }