Commit fd735c8c authored by Juho Snellman's avatar Juho Snellman

More documentation

parent ef4c70a3
...@@ -294,6 +294,7 @@ bool test_single_timer_random() { ...@@ -294,6 +294,7 @@ bool test_single_timer_random() {
int r = 1 + rand() % ( 1 << len); int r = 1 + rand() % ( 1 << len);
timers.schedule(&timer, r); timers.schedule(&timer, r);
if (r > 1)
timers.advance(r - 1); timers.advance(r - 1);
EXPECT_INTEQ(count, i); EXPECT_INTEQ(count, i);
timers.advance(1); timers.advance(1);
......
...@@ -2,6 +2,38 @@ ...@@ -2,6 +2,38 @@
// //
// Copyright 2016 Juho Snellman, released under a MIT license (see // Copyright 2016 Juho Snellman, released under a MIT license (see
// LICENSE). // LICENSE).
//
// A timer queue which allows events to be scheduled for execution
// at some later point. Reasons you might want to use this implementation
// instead of some other are:
//
// - Optimized for high occupancy rates, on the assumption that the
// utilization of the timer queue is proportional to the utilization
// of the system as a whole. When a tradeoff needs to be made
// between efficiency of one operation at a low occupancy rate and
// another operation at a high rate, we choose the latter.
// - Tries to minimize the cost of event rescheduling or cancelation,
// on the assumption that a large percentage of timers will never
// run. The implementation tries avoids unnecessary work when an
// event is rescheduled, and provides a way for the user specify a
// range of acceptable execution times instead of just an exact one.
// - An interface that at least the author finds more convenient than
// the typical options.
//
// The exact implementation strategy is a hierarchical timer
// wheel. A timer wheel is effectively a ring buffer of linked lists
// of events, and a pointer to the ring buffer. As the time advances,
// the pointer moves forward, and any events in the ring buffer slots
// that the pointer passed will get executed.
//
// A hierarchical timer wheel layers multiple timer wheels running at
// different resolutions on top of each other. When an event is
// scheduled so far in the future than it does not fit the innermost
// (core) wheel, it instead gets scheduled on one of the outer
// wheels. On each rotation of the inner wheel, one slot's worth of
// events are promoted from the second wheel to the core. On each
// rotation of the second wheel, one slot's worth of events is
// promoted from the third wheel to the second, and so on.
#ifndef _TIMER_WHEEL_H #ifndef _TIMER_WHEEL_H
#define _TIMER_WHEEL_H #define _TIMER_WHEEL_H
...@@ -131,12 +163,20 @@ private: ...@@ -131,12 +163,20 @@ private:
TimerEventInterface* events_ = NULL; TimerEventInterface* events_ = NULL;
}; };
// A TimerWheel is the entity that TimerEvents can be scheduled on
// for execution (with schedule() or schedule_in_range()), and will
// eventually be executed once the time advances far enough with the
// advance() method.
//
// When the core timer wheel is created by the user, the appropriate
// outer wheels will be created automatically. The outer wheels are
// not accessible to the user.
class TimerWheel { class TimerWheel {
public: public:
TimerWheel(Tick now = 0) TimerWheel(Tick now = 0)
: now_(now), : now_(now),
up_(new TimerWheel(WIDTH_BITS, this)), out_(new TimerWheel(WIDTH_BITS, this)),
down_(NULL) { core_(NULL) {
} }
// Advance the TimerWheel by the specified number of ticks, and execute // Advance the TimerWheel by the specified number of ticks, and execute
...@@ -150,11 +190,14 @@ public: ...@@ -150,11 +190,14 @@ public:
void advance(Tick delta); void advance(Tick delta);
// Schedule the event to be executed delta ticks from the current time. // Schedule the event to be executed delta ticks from the current time.
// The delta must be non-0.
void schedule(TimerEventInterface* event, Tick delta); void schedule(TimerEventInterface* event, Tick delta);
// Schedule the event to happen at some time between start and end // Schedule the event to happen at some time between start and end
// ticks from the current time. The actual time will be determined // ticks from the current time. The actual time will be determined
// by the TimerWheel to minimize rescheduling and promotion overhead. // by the TimerWheel to minimize rescheduling and promotion overhead.
// Both start and end must be non-0, and the end must be greater than
// the start.
void schedule_in_range(TimerEventInterface* event, void schedule_in_range(TimerEventInterface* event,
Tick start, Tick end); Tick start, Tick end);
...@@ -175,20 +218,27 @@ private: ...@@ -175,20 +218,27 @@ private:
TimerWheel(int offset, TimerWheel* down) TimerWheel(int offset, TimerWheel* down)
: now_(0), : now_(0),
down_(down) { core_(down) {
if (offset + WIDTH_BITS < 64) { if (offset + WIDTH_BITS < 64) {
up_.reset(new TimerWheel(offset + WIDTH_BITS, down)); out_.reset(new TimerWheel(offset + WIDTH_BITS, down));
} }
} }
// The current timestamp for this wheel. This will be right-shifted
// such that each slot is separated by exactly one tick even on
// the outermost wheels.
Tick now_; Tick now_;
static const int WIDTH_BITS = 8; static const int WIDTH_BITS = 8;
static const int NUM_SLOTS = 1 << WIDTH_BITS; static const int NUM_SLOTS = 1 << WIDTH_BITS;
// A bitmask for looking at just the bits in the timestamp relevant to
// this wheel.
static const int MASK = (NUM_SLOTS - 1); static const int MASK = (NUM_SLOTS - 1);
TimerWheelSlot slots_[NUM_SLOTS]; TimerWheelSlot slots_[NUM_SLOTS];
std::unique_ptr<TimerWheel> up_; // The next timer wheel layer (coarser granularity).
TimerWheel* down_; std::unique_ptr<TimerWheel> out_;
// The core timer wheel (most granular).
TimerWheel* core_;
}; };
// Implementation // Implementation
...@@ -240,22 +290,23 @@ void TimerEventInterface::cancel() { ...@@ -240,22 +290,23 @@ void TimerEventInterface::cancel() {
} }
void TimerWheel::advance(Tick delta) { void TimerWheel::advance(Tick delta) {
assert(delta > 0);
while (delta--) { while (delta--) {
now_++; now_++;
size_t slot_index = now_ & MASK; size_t slot_index = now_ & MASK;
auto slot = &slots_[slot_index]; auto slot = &slots_[slot_index];
if (slot_index == 0 && up_) { if (slot_index == 0 && out_) {
up_->advance(1); out_->advance(1);
} }
while (slot->events()) { while (slot->events()) {
auto event = slot->pop_event(); auto event = slot->pop_event();
if (down_) { if (core_) {
assert((down_->now_ & MASK) == 0); assert((core_->now_ & MASK) == 0);
Tick now = down_->now(); Tick now = core_->now();
if (now >= event->scheduled_at()) { if (now >= event->scheduled_at()) {
event->execute(); event->execute();
} else { } else {
down_->schedule(event, core_->schedule(event,
event->scheduled_at() - now); event->scheduled_at() - now);
} }
} else { } else {
...@@ -266,12 +317,14 @@ void TimerWheel::advance(Tick delta) { ...@@ -266,12 +317,14 @@ void TimerWheel::advance(Tick delta) {
} }
void TimerWheel::schedule(TimerEventInterface* event, Tick delta) { void TimerWheel::schedule(TimerEventInterface* event, Tick delta) {
if (!down_) { assert(delta > 0);
if (!core_) {
event->set_scheduled_at(now_ + delta); event->set_scheduled_at(now_ + delta);
} }
if (delta >= NUM_SLOTS) { if (delta >= NUM_SLOTS) {
return up_->schedule(event, (delta + (now_ & MASK)) >> WIDTH_BITS); return out_->schedule(event, (delta + (now_ & MASK)) >> WIDTH_BITS);
} }
size_t slot_index = (now_ + delta) & MASK; size_t slot_index = (now_ + delta) & MASK;
...@@ -308,7 +361,7 @@ void TimerWheel::schedule_in_range(TimerEventInterface* event, ...@@ -308,7 +361,7 @@ void TimerWheel::schedule_in_range(TimerEventInterface* event,
Tick TimerWheel::ticks_to_next_event(const Tick& max) { Tick TimerWheel::ticks_to_next_event(const Tick& max) {
// The actual current time (not the bitshifted time) // The actual current time (not the bitshifted time)
Tick now = down_ ? down_->now() : now_; Tick now = core_ ? core_->now() : now_;
// Smallest tick (relative to now) we've found. // Smallest tick (relative to now) we've found.
Tick min = max; Tick min = max;
...@@ -323,8 +376,8 @@ Tick TimerWheel::ticks_to_next_event(const Tick& max) { ...@@ -323,8 +376,8 @@ Tick TimerWheel::ticks_to_next_event(const Tick& max) {
// can't just accept those results outright, we need to // can't just accept those results outright, we need to
// check the best result there against the next slot on // check the best result there against the next slot on
// this wheel. // this wheel.
if (slot_index == 0 && up_) { if (slot_index == 0 && out_) {
const auto& slot = up_->slots_[(up_->now_ + 1) & MASK]; const auto& slot = out_->slots_[(out_->now_ + 1) & MASK];
for (auto event = slot.events(); event != NULL; for (auto event = slot.events(); event != NULL;
event = event->next_) { event = event->next_) {
min = std::min(min, event->scheduled_at() - now); min = std::min(min, event->scheduled_at() - now);
...@@ -343,8 +396,8 @@ Tick TimerWheel::ticks_to_next_event(const Tick& max) { ...@@ -343,8 +396,8 @@ Tick TimerWheel::ticks_to_next_event(const Tick& max) {
} }
// Nothing found on this wheel, try the next one. // Nothing found on this wheel, try the next one.
if (up_) { if (out_) {
return up_->ticks_to_next_event(max); return out_->ticks_to_next_event(max);
} }
return max; return max;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment