Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
R
Ratas
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kirill Smelkov
Ratas
Commits
fd735c8c
Commit
fd735c8c
authored
Jul 22, 2016
by
Juho Snellman
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
More documentation
parent
ef4c70a3
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
74 additions
and
20 deletions
+74
-20
src/test/test_basic.cc
src/test/test_basic.cc
+2
-1
src/timer-wheel.h
src/timer-wheel.h
+72
-19
No files found.
src/test/test_basic.cc
View file @
fd735c8c
...
...
@@ -294,7 +294,8 @@ bool test_single_timer_random() {
int
r
=
1
+
rand
()
%
(
1
<<
len
);
timers
.
schedule
(
&
timer
,
r
);
timers
.
advance
(
r
-
1
);
if
(
r
>
1
)
timers
.
advance
(
r
-
1
);
EXPECT_INTEQ
(
count
,
i
);
timers
.
advance
(
1
);
EXPECT_INTEQ
(
count
,
i
+
1
);
...
...
src/timer-wheel.h
View file @
fd735c8c
...
...
@@ -2,6 +2,38 @@
//
// Copyright 2016 Juho Snellman, released under a MIT license (see
// 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
#define _TIMER_WHEEL_H
...
...
@@ -131,12 +163,20 @@ private:
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
{
public:
TimerWheel
(
Tick
now
=
0
)
:
now_
(
now
),
up
_
(
new
TimerWheel
(
WIDTH_BITS
,
this
)),
down
_
(
NULL
)
{
out
_
(
new
TimerWheel
(
WIDTH_BITS
,
this
)),
core
_
(
NULL
)
{
}
// Advance the TimerWheel by the specified number of ticks, and execute
...
...
@@ -150,11 +190,14 @@ public:
void
advance
(
Tick
delta
);
// Schedule the event to be executed delta ticks from the current time.
// The delta must be non-0.
void
schedule
(
TimerEventInterface
*
event
,
Tick
delta
);
// Schedule the event to happen at some time between start and end
// ticks from the current time. The actual time will be determined
// 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
,
Tick
start
,
Tick
end
);
...
...
@@ -175,20 +218,27 @@ private:
TimerWheel
(
int
offset
,
TimerWheel
*
down
)
:
now_
(
0
),
down
_
(
down
)
{
core
_
(
down
)
{
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_
;
static
const
int
WIDTH_BITS
=
8
;
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
);
TimerWheelSlot
slots_
[
NUM_SLOTS
];
std
::
unique_ptr
<
TimerWheel
>
up_
;
TimerWheel
*
down_
;
// The next timer wheel layer (coarser granularity).
std
::
unique_ptr
<
TimerWheel
>
out_
;
// The core timer wheel (most granular).
TimerWheel
*
core_
;
};
// Implementation
...
...
@@ -240,22 +290,23 @@ void TimerEventInterface::cancel() {
}
void
TimerWheel
::
advance
(
Tick
delta
)
{
assert
(
delta
>
0
);
while
(
delta
--
)
{
now_
++
;
size_t
slot_index
=
now_
&
MASK
;
auto
slot
=
&
slots_
[
slot_index
];
if
(
slot_index
==
0
&&
up
_
)
{
up
_
->
advance
(
1
);
if
(
slot_index
==
0
&&
out
_
)
{
out
_
->
advance
(
1
);
}
while
(
slot
->
events
())
{
auto
event
=
slot
->
pop_event
();
if
(
down
_
)
{
assert
((
down
_
->
now_
&
MASK
)
==
0
);
Tick
now
=
down
_
->
now
();
if
(
core
_
)
{
assert
((
core
_
->
now_
&
MASK
)
==
0
);
Tick
now
=
core
_
->
now
();
if
(
now
>=
event
->
scheduled_at
())
{
event
->
execute
();
}
else
{
down
_
->
schedule
(
event
,
core
_
->
schedule
(
event
,
event
->
scheduled_at
()
-
now
);
}
}
else
{
...
...
@@ -266,12 +317,14 @@ void TimerWheel::advance(Tick delta) {
}
void
TimerWheel
::
schedule
(
TimerEventInterface
*
event
,
Tick
delta
)
{
if
(
!
down_
)
{
assert
(
delta
>
0
);
if
(
!
core_
)
{
event
->
set_scheduled_at
(
now_
+
delta
);
}
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
;
...
...
@@ -308,7 +361,7 @@ void TimerWheel::schedule_in_range(TimerEventInterface* event,
Tick
TimerWheel
::
ticks_to_next_event
(
const
Tick
&
max
)
{
// 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.
Tick
min
=
max
;
...
...
@@ -323,8 +376,8 @@ Tick TimerWheel::ticks_to_next_event(const Tick& max) {
// can't just accept those results outright, we need to
// check the best result there against the next slot on
// this wheel.
if
(
slot_index
==
0
&&
up
_
)
{
const
auto
&
slot
=
up_
->
slots_
[(
up
_
->
now_
+
1
)
&
MASK
];
if
(
slot_index
==
0
&&
out
_
)
{
const
auto
&
slot
=
out_
->
slots_
[(
out
_
->
now_
+
1
)
&
MASK
];
for
(
auto
event
=
slot
.
events
();
event
!=
NULL
;
event
=
event
->
next_
)
{
min
=
std
::
min
(
min
,
event
->
scheduled_at
()
-
now
);
...
...
@@ -343,8 +396,8 @@ Tick TimerWheel::ticks_to_next_event(const Tick& max) {
}
// Nothing found on this wheel, try the next one.
if
(
up
_
)
{
return
up
_
->
ticks_to_next_event
(
max
);
if
(
out
_
)
{
return
out
_
->
ticks_to_next_event
(
max
);
}
return
max
;
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment