From beef9c256748abc60610882dae80739280c31651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Fri, 21 Jul 2017 21:34:34 +0200 Subject: [PATCH 1/2] Add a doubly linked list implementation. --- source/eventcore/internal/dlist.d | 155 ++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 source/eventcore/internal/dlist.d diff --git a/source/eventcore/internal/dlist.d b/source/eventcore/internal/dlist.d new file mode 100644 index 0000000..5186fc8 --- /dev/null +++ b/source/eventcore/internal/dlist.d @@ -0,0 +1,155 @@ +module eventcore.internal.dlist; + +struct StackDList(T) { + @safe nothrow @nogc: + + private { + T* m_first, m_last; + size_t m_length; + } + + @disable this(this); + + void clear() + { + T* itm = m_first; + while (itm) { + auto next = itm.next; + itm.prev = null; + itm.next = null; + itm = next; + } + m_length = 0; + } + + @property bool empty() const { return m_first is null; } + + @property size_t length() const { return m_length; } + + @property T* front() { return m_first; } + @property T* back() { return m_last; } + + void insertFront(T* item) + { + assert(!item.prev && !item.next); + item.next = m_first; + if (m_first) { + m_first.prev = item; + m_first = item; + } else { + m_last = item; + m_first = item; + } + m_length++; + } + + void insertBack(T* item) + { + assert(!item.prev && !item.next); + item.prev = m_last; + if (m_last) { + m_last.next = item; + m_last = item; + } else { + m_first = item; + m_last = item; + } + m_length++; + } + + void insertAfter(T* item, T* after) + { + if (!after) insertBack(item); + else { + item.prev = after; + item.next = after.next; + after.next = item; + if (item.next) item.next.prev = item; + else m_last = item; + } + m_length++; + } + + void remove(T* item) + { + if (item.prev) item.prev.next = item.next; + else m_first = item.next; + if (item.next) item.next.prev = item.prev; + else m_last = item.prev; + item.prev = null; + item.next = null; + m_length--; + } + + void removeFront() + { + if (m_first) remove(m_first); + m_length--; + } + + void removeBack() + { + if (m_last) remove(m_last); + m_length--; + } + + static struct Range { + private T* m_first, m_last; + + this(T* first, T* last) + { + m_first = first; + m_last = last; + } + + @property bool empty() const { return m_first is null; } + @property T* front() { return m_first; } + @property T* back() { return m_last; } + + void popFront() { + assert(m_first !is null); + m_first = m_first.next; + if (!m_first) m_last = null; + } + + void popBack() { + assert(m_last !is null); + m_last = m_last.prev; + if (!m_last) m_first = null; + } + } + + Range opSlice() { return Range(m_first, m_last); } +} + +unittest { + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + + struct S { size_t i; S* prev, next; } + + S[10] s; + foreach (i, ref rs; s) rs.i = i; + + StackDList!S list; + + assert(list.empty); + assert(list[].empty); + list.insertBack(&s[0]); + assert(list[].map!(s => s.i).equal([0])); + list.insertBack(&s[1]); + assert(list[].map!(s => s.i).equal([0, 1])); + list.insertAfter(&s[2], &s[0]); + assert(list[].map!(s => s.i).equal([0, 2, 1])); + list.insertFront(&s[3]); + assert(list[].map!(s => s.i).equal([3, 0, 2, 1])); + list.removeBack(); + assert(list[].map!(s => s.i).equal([3, 0, 2])); + list.remove(&s[0]); + assert(list[].map!(s => s.i).equal([3, 2])); + list.remove(&s[3]); + assert(list[].map!(s => s.i).equal([2])); + list.remove(&s[2]); + assert(list.empty); + assert(list[].empty); +} From 09a67149bcecee5ea5d68fd5538b2b4a1143ff96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Thu, 20 Jul 2017 22:59:21 +0200 Subject: [PATCH 2/2] Use a linked list for the timer queue. Improves insertion and deletion times for many pending timers considerably. --- source/eventcore/drivers/timer.d | 60 ++++++++++++++------------------ 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/source/eventcore/drivers/timer.d b/source/eventcore/drivers/timer.d index f0d7b1a..a9bbfba 100644 --- a/source/eventcore/drivers/timer.d +++ b/source/eventcore/drivers/timer.d @@ -4,6 +4,7 @@ module eventcore.drivers.timer; import eventcore.driver; +import eventcore.internal.dlist; final class LoopTimeoutTimerDriver : EventDriverTimers { @@ -20,7 +21,7 @@ final class LoopTimeoutTimerDriver : EventDriverTimers { private { static FreeList!(Mallocator, TimerSlot.sizeof) ms_allocator; TimerSlot*[TimerID] m_timers; - Array!(TimerSlot*) m_timerQueue; + StackDList!TimerSlot m_timerQueue; TimerID m_lastTimerID; TimerSlot*[] m_firedTimers; } @@ -34,7 +35,7 @@ final class LoopTimeoutTimerDriver : EventDriverTimers { final package Duration getNextTimeout(long stdtime) @safe nothrow { - if (m_timerQueue.length == 0) return Duration.max; + if (m_timerQueue.empty) return Duration.max; return (m_timerQueue.front.timeout - stdtime).hnsecs; } @@ -45,33 +46,26 @@ final class LoopTimeoutTimerDriver : EventDriverTimers { TimerSlot ts = void; ts.timeout = stdtime+1; - auto fired = m_timerQueue[].assumeSorted!((a, b) => a.timeout < b.timeout).lowerBound(&ts); - foreach (tm; fired) { + foreach (tm; m_timerQueue[]) { + if (tm.timeout > stdtime) break; if (tm.repeatDuration > 0) { do tm.timeout += tm.repeatDuration; while (tm.timeout <= stdtime); - auto tail = m_timerQueue[fired.length .. $].assumeSorted!((a, b) => a.timeout < b.timeout).upperBound(tm); - try m_timerQueue.insertBefore(tail.release, tm); - catch (Exception e) { assert(false, e.msg); } + enqueueTimer(tm); } else tm.pending = false; m_firedTimers ~= tm; } // NOTE: this isn't yet verified to work under all circumstances - auto elems = m_timerQueue[0 .. fired.length]; - - { - scope (failure) assert(false); - m_timerQueue.linearRemove(elems); - } + foreach (tm; m_firedTimers) + m_timerQueue.remove(tm); foreach (tm; m_firedTimers) { auto cb = tm.callback; tm.callback = null; - if (cb) cb(tm.id); } - + bool any_fired = m_firedTimers.length > 0; m_firedTimers.length = 0; @@ -103,10 +97,7 @@ final class LoopTimeoutTimerDriver : EventDriverTimers { tm.timeout = Clock.currStdTime + timeout.total!"hnsecs"; tm.repeatDuration = repeat.total!"hnsecs"; tm.pending = true; - - auto largerRange = m_timerQueue[].assumeSorted!((a, b) => a.timeout < b.timeout).upperBound(tm); - try m_timerQueue.insertBefore(largerRange.release, tm); - catch (Exception e) { assert(false, e.msg); } + enqueueTimer(tm); } final override void stop(TimerID timer) @@ -114,20 +105,7 @@ final class LoopTimeoutTimerDriver : EventDriverTimers { auto tm = m_timers[timer]; if (!tm.pending) return; tm.pending = false; - - TimerSlot cmp = void; - cmp.timeout = tm.timeout-1; - auto upper = m_timerQueue[].assumeSorted!((a, b) => a.timeout < b.timeout).upperBound(&cmp); - assert(!upper.empty); - while (!upper.empty) { - assert(upper.front.timeout == tm.timeout); - if (upper.front is tm) { - scope (failure) assert(false); - m_timerQueue.linearRemove(upper.release.take(1)); - break; - } - upper.popFront(); - } + m_timerQueue.remove(tm); } final override bool isPending(TimerID descriptor) @@ -177,7 +155,7 @@ final class LoopTimeoutTimerDriver : EventDriverTimers { ms_allocator.dispose(tm); GC.removeRange(tm); } (); - + return false; } @@ -189,9 +167,23 @@ final class LoopTimeoutTimerDriver : EventDriverTimers { if (descriptor == TimerID.init) return false; return m_timers[descriptor].refCount == 1; } + + private void enqueueTimer(TimerSlot* tm) + nothrow { + TimerSlot* ns; + foreach_reverse (t; m_timerQueue[]) + if (t.timeout <= tm.timeout) { + ns = t; + break; + } + + if (ns) m_timerQueue.insertAfter(tm, ns); + else m_timerQueue.insertFront(tm); + } } struct TimerSlot { + TimerSlot* prev, next; TimerID id; uint refCount; bool pending;