1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//! What `Event`'s listen and notify paths cost: taking a listener, alone and dropped at once,
//! sending a notification to nobody, waking one listener and a hundred taken beforehand, and
//! `lock::Mutex` and `lock::Semaphore`, built on an event, taken by one thread alone. `Event` needs
//! no runtime, so no id here goes through `zruntime::block_on`: they poll listeners by hand or take
//! the mutex with `try_lock` and a permit with `try_acquire`, all on one thread.
//! `mutex/4-threads`, the mutex taken by four contending threads, is in `contention.rs`, as only
//! the clock can measure it.
//!
//! Each id but `wake-all/100` repeats what it times within one timed call, [`OPS`] times or, for
//! `listen` and `wake-one`, [`LISTENERS_PER_BATCH`] times, and its throughput is in those
//! repetitions. CodSpeed's CPU simulation times a single call of each id, which starts with nothing
//! in the simulated cache. A call of a few dozen instructions, such as one notification sent to
//! nobody, then costs mostly the cache lines it touches: one line more or less, as where the stack
//! starts moves with the size of the environment, moves its time by more than the 10% that CodSpeed
//! reports as a change. A call that repeats it pays for those lines once, and costs what its
//! instructions do.
//!
//! `listen` times taking a listener, alone, on an event whose shared state is already allocated:
//! what every wait starts with. The listeners a call takes are dropped once it has been timed, so
//! the event's queue holds a batch of them at most.
//!
//! `listen-drop` times taking a listener and dropping it at once, on an event whose shared state
//! is already allocated: the common case of a listener whose wait never has to happen because the
//! condition it was taken for already held.
//!
//! `notify-none` times `notify(1)` on an initialised event with nobody listening: the path a
//! lock's release takes when nobody is waiting for it, and so what `notify` costs even where
//! there is nothing to wake.
//!
//! `mutex/uncontended` times taking and releasing zruntime's own `lock::Mutex` on one thread, with
//! nobody else after it: the commonest use of a lock, whose release notifies the mutex's event with
//! nobody listening.
//!
//! `semaphore/uncontended` times taking a permit of zruntime's own `lock::Semaphore` and giving it
//! back on one thread, with nobody else after one: what a semaphore that caps work which seldom
//! reaches the cap costs, whose release notifies its event with nobody listening.
//!
//! `wake-one` times notifying one listener and polling it once to `Ready`: the end of a wait,
//! from being told it is over to finding so, without its start, which `listen` times.
//!
//! `wake-all/100` times waking a hundred listeners with one `notify(usize::MAX)` and polling each
//! to `Ready`: a broadcast to every listener an event has, such as a connection announcing that it
//! has closed.
//!
//! Every event that `wake-one` and `wake-all/100` notify is one of their own, made and listened to
//! before the timing starts: a call of `wake-one` notifies [`LISTENERS_PER_BATCH`] events with a
//! listener each, and one of `wake-all/100` an event with a hundred. The listeners a call polls are
//! then the only ones its notifications can reach, however many calls a harness sets up before it
//! runs them: CodSpeed's CPU simulation sets up two for the one it measures.
use ;
use ;
use ;
/// Times `listen`, `notify` and the poll that resolves a listener, all on a single thread.
/// A new event, and `count` listeners taken on it, the oldest first.
/// Polls `listener` once, with a waker that goes nowhere, and hands back what the poll found.
/// How many times a timed call of `listen-drop`, `notify-none`, `mutex/uncontended` and
/// `semaphore/uncontended` repeats what it times: enough that the call costs what its instructions
/// do, rather than the cache lines it touches (see the module documentation).
const OPS: u64 = 1000;
/// How many listeners a timed call of `listen` takes, and how many events one of `wake-one` wakes:
/// few enough to keep the queue of the event `listen` takes its listeners on short, and enough that
/// a call costs what its instructions do, rather than the cache lines it touches, and that reading
/// the clock once a call costs little next to them.
const LISTENERS_PER_BATCH: usize = 64;
criterion_group!;
criterion_main!;