Commit 31362c98 authored by Juho Snellman's avatar Juho Snellman

Merge the separate wheel layers together in one object

- No benefit to reifying them as separate objects. Merging the
  separate logical objects to one physical one gives better
  cache utilization and simplifies some algorithms.
parent bc27c0f2
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
#define _TIMER_WHEEL_H #define _TIMER_WHEEL_H
#include <cassert> #include <cassert>
#include <cmath>
#include <cstdlib> #include <cstdlib>
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
...@@ -211,16 +212,12 @@ private: ...@@ -211,16 +212,12 @@ private:
// for execution (with schedule() or schedule_in_range()), and will // for execution (with schedule() or schedule_in_range()), and will
// eventually be executed once the time advances far enough with the // eventually be executed once the time advances far enough with the
// advance() method. // 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), for (int i = 0; i < NUM_LEVELS; ++i) {
out_(new TimerWheel(now, WIDTH_BITS, this)), now_[i] = now >> (WIDTH_BITS * i);
core_(NULL) { }
} }
// Advance the TimerWheel by the specified number of ticks, and execute // Advance the TimerWheel by the specified number of ticks, and execute
...@@ -231,7 +228,7 @@ public: ...@@ -231,7 +228,7 @@ public:
// be advanced to. // be advanced to.
// - Events will happen in order; all events scheduled for tick X will // - Events will happen in order; all events scheduled for tick X will
// be executed before any event scheduled for tick X+1. // be executed before any event scheduled for tick X+1.
void advance(Tick delta); void advance(Tick delta, int level = 0);
// 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. // The delta must be non-0.
...@@ -249,44 +246,32 @@ public: ...@@ -249,44 +246,32 @@ public:
// by multiple ticks during a single call to advance(), during the // by multiple ticks during a single call to advance(), during the
// execution of the event callback now() will return the tick that // execution of the event callback now() will return the tick that
// the event was scheduled to run on. // the event was scheduled to run on.
Tick now() const { return now_; } Tick now() const { return now_[0]; }
// Return the number of ticks remaining until the next event will get // Return the number of ticks remaining until the next event will get
// executed. If the max parameter is passed, that will be the maximum // executed. If the max parameter is passed, that will be the maximum
// tick value that gets returned. The max parameter's value will also // tick value that gets returned. The max parameter's value will also
// be returned if no events have been scheduled. // be returned if no events have been scheduled.
Tick ticks_to_next_event(const Tick& max = std::numeric_limits<Tick>::max()); Tick ticks_to_next_event(const Tick& max = std::numeric_limits<Tick>::max(),
int level = 0);
private: private:
TimerWheel(const TimerWheel& other) = delete; TimerWheel(const TimerWheel& other) = delete;
TimerWheel& operator=(const TimerWheel& other) = delete; TimerWheel& operator=(const TimerWheel& other) = delete;
TimerWheel(Tick now, int offset, TimerWheel* down)
: now_(now >> offset),
shift_(offset),
core_(down) {
if (offset + WIDTH_BITS < 64) {
out_.reset(new TimerWheel(now, 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_;
// The amount that the tick has been right-shifted by.
int shift_ = 0;
static const int WIDTH_BITS = 8; static const int WIDTH_BITS = 8;
static const int NUM_LEVELS = std::ceil(64 / WIDTH_BITS);
static const int MAX_LEVEL = NUM_LEVELS - 1;
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 // A bitmask for looking at just the bits in the timestamp relevant to
// this wheel. // this wheel.
static const int MASK = (NUM_SLOTS - 1); static const int MASK = (NUM_SLOTS - 1);
TimerWheelSlot slots_[NUM_SLOTS];
// The next timer wheel layer (coarser granularity). // The current timestamp for this wheel. This will be right-shifted
std::unique_ptr<TimerWheel> out_; // such that each slot is separated by exactly one tick even on
// The core timer wheel (most granular). // the outermost wheels.
TimerWheel* core_; Tick now_[NUM_LEVELS];
TimerWheelSlot slots_[NUM_LEVELS][NUM_SLOTS];
}; };
// Implementation // Implementation
...@@ -337,25 +322,24 @@ void TimerEventInterface::cancel() { ...@@ -337,25 +322,24 @@ void TimerEventInterface::cancel() {
relink(NULL); relink(NULL);
} }
void TimerWheel::advance(Tick delta) { void TimerWheel::advance(Tick delta, int level) {
assert(delta > 0); assert(delta > 0);
while (delta--) { while (delta--) {
now_++; Tick now = ++now_[level];
size_t slot_index = now_ & MASK; size_t slot_index = now & MASK;
auto slot = &slots_[slot_index]; auto slot = &slots_[level][slot_index];
if (slot_index == 0 && out_) { if (slot_index == 0 && level < MAX_LEVEL) {
out_->advance(1); advance(1, level + 1);
} }
while (slot->events()) { while (slot->events()) {
auto event = slot->pop_event(); auto event = slot->pop_event();
if (core_) { if (level > 0) {
assert((core_->now_ & MASK) == 0); assert((now_[0] & MASK) == 0);
Tick now = core_->now(); if (now_[0] >= event->scheduled_at()) {
if (now >= event->scheduled_at()) {
event->execute(); event->execute();
} else { } else {
core_->schedule(event, schedule(event,
event->scheduled_at() - now); event->scheduled_at() - now_[0]);
} }
} else { } else {
event->execute(); event->execute();
...@@ -366,17 +350,16 @@ void TimerWheel::advance(Tick delta) { ...@@ -366,17 +350,16 @@ void TimerWheel::advance(Tick delta) {
void TimerWheel::schedule(TimerEventInterface* event, Tick delta) { void TimerWheel::schedule(TimerEventInterface* event, Tick delta) {
assert(delta > 0); assert(delta > 0);
event->set_scheduled_at(now_[0] + delta);
if (!core_) { int level = 0;
event->set_scheduled_at(now_ + delta); while (delta >= NUM_SLOTS) {
} delta = (delta + (now_[level] & MASK)) >> WIDTH_BITS;
++level;
if (delta >= NUM_SLOTS) {
return out_->schedule(event, (delta + (now_ & MASK)) >> WIDTH_BITS);
} }
size_t slot_index = (now_ + delta) & MASK; size_t slot_index = (now_[level] + delta) & MASK;
auto slot = &slots_[slot_index]; auto slot = &slots_[level][slot_index];
event->relink(slot); event->relink(slot);
} }
...@@ -384,7 +367,7 @@ void TimerWheel::schedule_in_range(TimerEventInterface* event, ...@@ -384,7 +367,7 @@ void TimerWheel::schedule_in_range(TimerEventInterface* event,
Tick start, Tick end) { Tick start, Tick end) {
assert(end > start); assert(end > start);
if (event->active()) { if (event->active()) {
auto current = event->scheduled_at() - now_; auto current = event->scheduled_at() - now_[0];
// Event is already scheduled to happen in this range. Instead // Event is already scheduled to happen in this range. Instead
// of always using the old slot, we could check compute the // of always using the old slot, we could check compute the
// new slot and switch iff it's aligned better than the old one. // new slot and switch iff it's aligned better than the old one.
...@@ -407,16 +390,16 @@ void TimerWheel::schedule_in_range(TimerEventInterface* event, ...@@ -407,16 +390,16 @@ void TimerWheel::schedule_in_range(TimerEventInterface* event,
schedule(event, delta); schedule(event, delta);
} }
Tick TimerWheel::ticks_to_next_event(const Tick& max) { Tick TimerWheel::ticks_to_next_event(const Tick& max, int level) {
// The actual current time (not the bitshifted time) // The actual current time (not the bitshifted time)
Tick now = core_ ? core_->now() : now_; Tick now = now_[0];
// Smallest tick (relative to now) we've found. // Smallest tick (relative to now) we've found.
Tick min = max; Tick min = max;
for (int i = 0; i < NUM_SLOTS; ++i) { for (int i = 0; i < NUM_SLOTS; ++i) {
// Note: Unlike the uses of "now", slot index calculations really // Note: Unlike the uses of "now", slot index calculations really
// need to use now_. // need to use now_.
auto slot_index = (now_ + 1 + i) & MASK; auto slot_index = (now_[level] + 1 + i) & MASK;
// We've reached slot 0. In normal scheduling this would // We've reached slot 0. In normal scheduling this would
// mean advancing the next wheel and promoting or executing // mean advancing the next wheel and promoting or executing
// those events. So we need to look in that slot too // those events. So we need to look in that slot too
...@@ -424,13 +407,14 @@ Tick TimerWheel::ticks_to_next_event(const Tick& max) { ...@@ -424,13 +407,14 @@ 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 && out_) { if (slot_index == 0 && level < MAX_LEVEL) {
// Exception: If we're in the core wheel, and slot 0 is // Exception: If we're in the core wheel, and slot 0 is
// not empty, there's no point in looking in the outer wheel. // not empty, there's no point in looking in the outer wheel.
// It's guaranteed that the events actually in slot 0 will be // It's guaranteed that the events actually in slot 0 will be
// executed no later than anything in the outer wheel. // executed no later than anything in the outer wheel.
if (core_ || !slots_[0].events()) { if (level > 0 || !slots_[level][slot_index].events()) {
const auto& slot = out_->slots_[(out_->now_ + 1) & MASK]; auto up_slot_index = (now_[level + 1] + 1) & MASK;
const auto& slot = slots_[level + 1][up_slot_index];
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);
...@@ -438,14 +422,14 @@ Tick TimerWheel::ticks_to_next_event(const Tick& max) { ...@@ -438,14 +422,14 @@ Tick TimerWheel::ticks_to_next_event(const Tick& max) {
} }
} }
bool found = false; bool found = false;
const auto& slot = slots_[slot_index]; const auto& slot = slots_[level][slot_index];
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);
// In the core wheel all the events in a slot are guaranteed to // In the core wheel all the events in a slot are guaranteed to
// run at the same time, so it's enough to just look at the first // run at the same time, so it's enough to just look at the first
// one. // one.
if (!core_) { if (level == 0) {
return min; return min;
} else { } else {
found = true; found = true;
...@@ -458,8 +442,9 @@ Tick TimerWheel::ticks_to_next_event(const Tick& max) { ...@@ -458,8 +442,9 @@ Tick TimerWheel::ticks_to_next_event(const Tick& max) {
// Nothing found on this wheel, try the next one (unless the wheel can't // Nothing found on this wheel, try the next one (unless the wheel can't
// possibly contain an event scheduled earlier than "max"). // possibly contain an event scheduled earlier than "max").
if (out_ && (max >> out_->shift_) > 0) { if (level < MAX_LEVEL &&
return out_->ticks_to_next_event(max); (max >> (WIDTH_BITS * level + 1)) > 0) {
return ticks_to_next_event(max, level + 1);
} }
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