vq / vlib / x / async / benchmarks / async_benchmark.v
270 lines · 246 sloc · 6.94 KB · a66aa5b0fe618b32988f4fd9b235017c336940ba
Raw
1import benchmark
2import context
3import os
4import time
5import x.async as xasync
6
7const default_group_rounds = 8
8const default_group_jobs = 16
9const default_task_rounds = 32
10const default_pool_jobs = 64
11const default_pool_workers = 4
12const default_pool_bounded_rounds = 8
13const default_pool_bounded_timeout_ms = 2
14const default_timeout_rounds = 32
15const default_every_iterations = 5
16const default_every_interval_ms = 1
17
18fn main() {
19 run_benchmarks() or {
20 eprintln('x.async benchmark failed: ${err.msg()}')
21 exit(1)
22 }
23}
24
25fn run_benchmarks() ! {
26 group_rounds := env_int('XASYNC_BENCH_GROUP_ROUNDS', default_group_rounds, 1, 200)
27 group_jobs := env_int('XASYNC_BENCH_GROUP_JOBS', default_group_jobs, 1, 512)
28 task_rounds := env_int('XASYNC_BENCH_TASK_ROUNDS', default_task_rounds, 1, 500)
29 pool_jobs := env_int('XASYNC_BENCH_POOL_JOBS', default_pool_jobs, 1, 1000)
30 pool_workers := env_int('XASYNC_BENCH_POOL_WORKERS', default_pool_workers, 1, 64)
31 pool_bounded_rounds := env_int('XASYNC_BENCH_POOL_BOUNDED_ROUNDS', default_pool_bounded_rounds,
32 1, 100)
33 pool_bounded_timeout_ms := env_int('XASYNC_BENCH_POOL_BOUNDED_TIMEOUT_MS',
34 default_pool_bounded_timeout_ms, 1, 100)
35 timeout_rounds := env_int('XASYNC_BENCH_TIMEOUT_ROUNDS', default_timeout_rounds, 1, 500)
36 every_iterations := env_int('XASYNC_BENCH_EVERY_ITERATIONS', default_every_iterations, 1, 100)
37 every_interval_ms :=
38 env_int('XASYNC_BENCH_EVERY_INTERVAL_MS', default_every_interval_ms, 1, 100)
39
40 println('x.async cautious benchmark')
41 println('Override sizes with XASYNC_BENCH_* environment variables.')
42
43 mut checksum := 0
44 mut b := benchmark.start()
45 checksum += bench_group(group_rounds, group_jobs)!
46 b.measure('Group: rounds=${group_rounds}, jobs_per_round=${group_jobs}, checksum=${checksum}')
47
48 checksum += bench_task(task_rounds)!
49 b.measure('Task: rounds=${task_rounds}, checksum=${checksum}')
50
51 checksum += bench_pool(pool_jobs, pool_workers)!
52 b.measure('Pool: jobs=${pool_jobs}, workers=${pool_workers}, checksum=${checksum}')
53
54 checksum += bench_pool_bounded_admission(pool_bounded_rounds, pool_bounded_timeout_ms)!
55 b.measure('Pool bounded admission: rounds=${pool_bounded_rounds}, timeout_ms=${pool_bounded_timeout_ms}, checksum=${checksum}')
56
57 checksum += bench_timeout(timeout_rounds)!
58 b.measure('with_timeout: rounds=${timeout_rounds}, checksum=${checksum}')
59
60 checksum += bench_every(every_iterations, every_interval_ms)!
61 b.measure('every: iterations=${every_iterations}, interval_ms=${every_interval_ms}, checksum=${checksum}')
62}
63
64fn env_int(name string, default_value int, min_value int, max_value int) int {
65 raw := os.getenv(name)
66 if raw == '' {
67 return default_value
68 }
69 mut value := raw.int()
70 if value < min_value {
71 value = min_value
72 }
73 if value > max_value {
74 value = max_value
75 }
76 return value
77}
78
79fn bench_group(rounds int, jobs_per_round int) !int {
80 mut total := 0
81 for _ in 0 .. rounds {
82 done := chan int{cap: jobs_per_round}
83 mut group := xasync.new_group(context.background())
84 for _ in 0 .. jobs_per_round {
85 group.go(fn [done] (mut ctx context.Context) ! {
86 _ = ctx
87 done <- 1
88 })!
89 }
90 group.wait()!
91 for _ in 0 .. jobs_per_round {
92 total += <-done
93 }
94 }
95 return total
96}
97
98fn bench_task(rounds int) !int {
99 mut total := 0
100 for i in 0 .. rounds {
101 mut task := xasync.run[int](fn [i] (mut ctx context.Context) !int {
102 _ = ctx
103 return i + 1
104 })!
105 total += task.wait()!
106 }
107 return total
108}
109
110fn bench_pool(jobs int, workers int) !int {
111 done := chan int{cap: jobs}
112 mut pool := xasync.new_pool(workers: workers, queue_size: jobs)!
113 for _ in 0 .. jobs {
114 pool.try_submit(fn [done] (mut ctx context.Context) ! {
115 _ = ctx
116 done <- 1
117 })!
118 }
119 pool.close()!
120 mut total := 0
121 for _ in 0 .. jobs {
122 total += <-done
123 }
124 return total
125}
126
127fn bench_pool_bounded_admission(rounds int, timeout_ms int) !int {
128 mut total := 0
129 admission_timeout := time.Duration(timeout_ms) * time.millisecond
130 for _ in 0 .. rounds {
131 mut pool := xasync.new_pool(workers: 1, queue_size: 1)!
132 started := chan bool{cap: 2}
133 release := chan bool{cap: 3}
134 finished := chan bool{cap: 2}
135 attempting := chan bool{cap: 1}
136 accepted := chan bool{cap: 1}
137 ran := chan int{cap: 1}
138 blocking_job := fn [started, release, finished] (mut ctx context.Context) ! {
139 _ = ctx
140 started <- true
141 _ := <-release
142 finished <- true
143 }
144
145 pool.try_submit(blocking_job)!
146 wait_for_benchmark_bool(started, 'bounded admission pool job did not start')!
147 pool.try_submit(blocking_job)!
148
149 mut timed_out := false
150 pool.submit_with_timeout(admission_timeout, fn (mut ctx context.Context) ! {
151 _ = ctx
152 }) or {
153 if err.msg() != 'async: timeout' {
154 return err
155 }
156 timed_out = true
157 }
158 if !timed_out {
159 release <- true
160 release <- true
161 pool.close()!
162 return error('submit_with_timeout accepted while the pool was full')
163 }
164
165 submit_thread := spawn fn [mut pool, attempting, accepted, ran] () {
166 attempting <- true
167 pool.submit_with_context(context.background(), fn [ran] (mut ctx context.Context) ! {
168 _ = ctx
169 ran <- 1
170 }) or {
171 accepted <- false
172 return
173 }
174 accepted <- true
175 }()
176 wait_for_benchmark_bool(attempting, 'bounded admission submitter did not start')!
177
178 release <- true
179 wait_for_benchmark_bool(finished, 'bounded admission first job did not finish')!
180 wait_for_benchmark_bool(accepted,
181 'submit_with_context did not accept after capacity opened')!
182 release <- true
183 total += read_benchmark_int(ran, 'bounded admission accepted job did not run')!
184 pool.close()!
185 submit_thread.wait()
186 }
187 return total
188}
189
190fn bench_timeout(rounds int) !int {
191 mut total := 0
192 for _ in 0 .. rounds {
193 xasync.with_timeout(1 * time.second, fn (mut ctx context.Context) ! {
194 _ = ctx
195 })!
196 total++
197 }
198 return total
199}
200
201fn bench_every(iterations int, interval_ms int) !int {
202 ctx, cancel := xasync.with_cancel()
203 ticks := chan int{cap: iterations + 4}
204 result := chan string{cap: 1}
205 interval := time.Duration(interval_ms) * time.millisecond
206 worker := spawn fn [ctx, ticks, result, interval] () {
207 xasync.every(ctx, interval, fn [ticks] (mut ctx context.Context) ! {
208 _ = ctx
209 ticks <- 1
210 }) or {
211 result <- err.msg()
212 return
213 }
214 result <- 'ok'
215 }()
216
217 mut total := 0
218 for _ in 0 .. iterations {
219 select {
220 value := <-ticks {
221 total += value
222 }
223 1 * time.second {
224 cancel()
225 worker.wait()
226 return error('every benchmark did not receive a tick')
227 }
228 }
229 }
230 cancel()
231 select {
232 msg := <-result {
233 if msg != 'context canceled' {
234 worker.wait()
235 return error('unexpected every benchmark result: ${msg}')
236 }
237 }
238 1 * time.second {
239 worker.wait()
240 return error('every benchmark did not stop after cancellation')
241 }
242 }
243 worker.wait()
244 return total
245}
246
247fn wait_for_benchmark_bool(signal chan bool, message string) ! {
248 select {
249 ok := <-signal {
250 if !ok {
251 return error(message)
252 }
253 }
254 1 * time.second {
255 return error(message)
256 }
257 }
258}
259
260fn read_benchmark_int(signal chan int, message string) !int {
261 select {
262 value := <-signal {
263 return value
264 }
265 1 * time.second {
266 return error(message)
267 }
268 }
269 return 0
270}
271