Here is a way to solve this problem doing
well on both time and space ('complexity')
and never using more than one 'timer
event' from the operating system services.
(1) Terminology
Let's start being somewhat clear on
terminology and then try to be more clear
by giving some hypothetical examples:
So, in our work, as time goes on, we
discover (receive) 'events'; we receive
these events at possibly some thousands or
more per second; each event has a 'time',
that is, 'clock on the wall' time, usually
in the future, when the event should cause
an 'action' (raise, trigger, generate an
interrupt, message, condition, etc.). For
the event that has the earliest time, we
want to cause the action at the time of
that event or as soon as we can after that
time.
So, by analogy, our problem is like a TO
DO list:
event = video conference with CA
time = 9:15 AM
action = start video software
event = take shower
time = 7:00 AM
action = wake SO
event = wake up
time = 6:45 AM
action = play Anvil Chorus
(2) First Grade Solution
Pick a positive integer n, say, 1024.
Go to the classic algorithm 'heap sort'
and allocate and define a heap in array
x(1), x(2), ..., x(n). Each component of
this array can be used to store the data
for some one event.
Let integer m be the number of events in
the heap now. Start with m = 0. We will
always have m <= n. In case after
allocating our array of n components we
discover that we have more events to store
than n, we modify this first grade
solution to the second grade solution
below.
In a heap, the components need to have an
'ordering' based on a 'key'. In our heap,
the keys are the clock on the wall times
of the events.
To review, for positive integer i = 1, 2,
..., m, for array component x(i), let
k(x(i)) be the value of that component's
key. Then the (ascending) 'heap
condition' is, for i = 2, 3, ..., m, and
letting i/2 be the integer part of this
quotient,
k(x(i/2)) < k(x(i))
So a heap is not really 'sorted' but is a
clever and 'implicit' way to store a 2-way
branched tree.
There are two 'heap operations', a faster
one and a slower one, used to establish
the heap condition. The faster operation
takes a heap of m < n components and a new
event, puts the event in component x(m +
1), considers the heap condition for i = m
+ 1 and exchanges if necessary, etc., and
finally sets m = m + 1. The slower
operation has a heap of m components, for
some i = 1, 2, ..., m - 1, removes the
event in component x(i), and works to put
an event in x(i) and establish a heap of m
- 1 components. For the details, see a
text on algorithms and data structures,
e.g., Knuth's 'The Art of Computer
Programming: Sorting and Searching' or
just work out the details as an exercise.
We want our heap to be in 'ascending'
order on time, that is, so that x(1) has
the event with the earliest time (as is
common and convenient, we assume that our
computer clock has enough 'resolution'
that each event time can be unique; as
below, having each time unique eases
removing events before their actions have
been taken in case we want to do that).
Suppose we receive an event:
If the time of this event is earlier than
the current time, then we just cause the
action of that event. Yes, for some
applications we might want to modify this
step if we anticipate some cases of events
arriving faster than we can take the
actions; I discuss this point below.
Else we put the event in component m + 1
of the array and perform (the faster) one
of the two classic heap operations to
establish the heap condition again.
If now m = 1, then we ask operating system
services for an interrupt at the time of
the event in array component x(1).
If m > 1 and establishing the heap
condition again changed the time of the
event in array component x(1), then we
cancel the operating system services
interrupt and ask for an interrupt at the
time for the event now in x(1).
When we get an interrupt, we take the
action of the event in array component
x(1) and set m = m - 1. Now array
component x(1) has no event and is
'empty'. So, we need to correct the
'empty' component x(1) and, in particular,
to establish the heap condition again. To
this end, if m > 0, then we use (the
slower) one of the two heap operations
and, then, ask operating system services
for an interrupt at the time of the event
now in array component x(1).
We can support canceling an event: Given
the time (again, each time is unique) of
the event, using the fact that the heap
really stores a binary tree, find the
event in the heap, remove it, and
establish the heap condition again.
(3) Related Application
Suppose we receive 2 billion numbers one
at a time and at the end want to have the
20 smallest. Or, if we want the 20
largest, then we use an ascending heap.
(4) Second Grade Solution
If we discover that n is too small, then
we regard the heap above as a 'leaf'.
Then for some positive integer k, we build
a k-way balanced tree where the leafs are
n component heaps as above and the nodes
are k-component heaps that point to nodes
or leafs in the tree farther from the root
of the tree.
When we need more nodes or leafs in the
tree, we allocate storage. As usual, we
may select a positive integer p and
actually never free node heaps to fewer
than p, and select positive integer q and
actually never free leaf storage to fewer
than q. In this way we can 'smooth out'
the number of storage allocation or free
requests to our dynamic storage allocation
manager.
Yes, here we might do some optimization
math starting with costs of storage
allocation and freeing and some
assumptions about the 'stochastic arrival
process' of the events. Due to the famous
renewal theorem in stochastic point
processes, we might assume that the
arrival process is just a non-stationary
Poisson process with slowly varying
arrival rate.
(5) Detail of Getting Too Busy
It may be that at times events arrive
faster than we can take their actions.
So, at 10:02 AM we may have 8,022 events
that have arrived but for which we have
taken no action and with all the times of
the events earlier than time 10:02 AM.
Now what? Well we might still like to
execute the events in ascending order on
time. So, we can go ahead and put the
events into the heap and/or balanced tree
storage until are 'caught up' and then
take the actions in ascending order on
time.
(6) Problem: How to modify for many
cores?