v4 / vlib / x / executor / benchmarks / executor_benchmark.v
190 lines · 173 sloc · 4.66 KB · e80386b6af8a071a2e13fd654ebcdb2105fb9202
Raw
1import benchmark
2import context
3import os
4import time
5import x.executor
6
7const default_drain_rounds = 16
8const default_drain_jobs = 32
9const default_sync_rounds = 32
10const default_timeout_rounds = 8
11const default_timeout_ms = 2
12const default_producers = 16
13
14fn main() {
15 run_benchmarks() or {
16 eprintln('x.executor benchmark failed: ${err.msg()}')
17 exit(1)
18 }
19}
20
21fn run_benchmarks() ! {
22 drain_rounds := env_int('XEXECUTOR_BENCH_DRAIN_ROUNDS', default_drain_rounds, 1, 200)
23 drain_jobs := env_int('XEXECUTOR_BENCH_DRAIN_JOBS', default_drain_jobs, 1, 512)
24 sync_rounds := env_int('XEXECUTOR_BENCH_SYNC_ROUNDS', default_sync_rounds, 1, 500)
25 timeout_rounds := env_int('XEXECUTOR_BENCH_TIMEOUT_ROUNDS', default_timeout_rounds, 1, 100)
26 timeout_ms := env_int('XEXECUTOR_BENCH_TIMEOUT_MS', default_timeout_ms, 1, 100)
27 producers := env_int('XEXECUTOR_BENCH_PRODUCERS', default_producers, 1, 256)
28
29 println('x.executor cautious benchmark')
30 println('Override sizes with XEXECUTOR_BENCH_* environment variables.')
31
32 mut checksum := 0
33 mut b := benchmark.start()
34 checksum += bench_try_post_drain(drain_rounds, drain_jobs)!
35 b.measure('try_post + drain_pending: rounds=${drain_rounds}, jobs=${drain_jobs}, checksum=${checksum}')
36
37 checksum += bench_run_sync(sync_rounds)!
38 b.measure('run_sync: rounds=${sync_rounds}, checksum=${checksum}')
39
40 checksum += bench_bounded_timeout(timeout_rounds, timeout_ms)!
41 b.measure('bounded admission timeout: rounds=${timeout_rounds}, timeout_ms=${timeout_ms}, checksum=${checksum}')
42
43 checksum += bench_many_producers(producers)!
44 b.measure('multi-producer post_with_context: producers=${producers}, checksum=${checksum}')
45}
46
47fn env_int(name string, default_value int, min_value int, max_value int) int {
48 raw := os.getenv(name)
49 if raw == '' {
50 return default_value
51 }
52 mut value := raw.int()
53 if value < min_value {
54 value = min_value
55 }
56 if value > max_value {
57 value = max_value
58 }
59 return value
60}
61
62fn bench_try_post_drain(rounds int, jobs int) !int {
63 mut total := 0
64 for _ in 0 .. rounds {
65 mut ex := executor.new(queue_size: jobs)!
66 done := chan int{cap: jobs}
67 for _ in 0 .. jobs {
68 ex.try_post(fn [done] () ! {
69 done <- 1
70 })!
71 }
72 drained := ex.drain_pending(jobs)!
73 total += drained
74 for _ in 0 .. jobs {
75 total += <-done
76 }
77 ex.stop()
78 ran_after_stop := ex.run_one()!
79 if ran_after_stop {
80 return error('try_post drain benchmark ran an unexpected job after stop')
81 }
82 ex.wait()!
83 }
84 return total
85}
86
87fn bench_run_sync(rounds int) !int {
88 mut ex := executor.new(queue_size: rounds)!
89 run_result := chan string{cap: 1}
90 runner := spawn fn [mut ex, run_result] () {
91 ex.run() or {
92 run_result <- err.msg()
93 return
94 }
95 run_result <- 'ok'
96 }()
97
98 done := chan int{cap: rounds}
99 for _ in 0 .. rounds {
100 ex.run_sync(fn [done] () ! {
101 done <- 1
102 })!
103 }
104
105 mut total := 0
106 for _ in 0 .. rounds {
107 total += <-done
108 }
109 ex.stop()
110 run_msg := <-run_result
111 if run_msg != 'ok' {
112 runner.wait()
113 return error('run_sync benchmark owner loop returned an error')
114 }
115 ex.wait()!
116 runner.wait()
117 return total
118}
119
120fn bench_bounded_timeout(rounds int, timeout_ms int) !int {
121 timeout := time.Duration(timeout_ms) * time.millisecond
122 mut total := 0
123 for _ in 0 .. rounds {
124 mut ex := executor.new(queue_size: 1)!
125 ex.try_post(fn () ! {})!
126 ex.post_with_timeout(timeout, fn () ! {}) or {
127 if err.msg() != 'executor: timeout' {
128 return err
129 }
130 total++
131 }
132 ran := ex.run_one()!
133 if !ran {
134 return error('bounded timeout benchmark did not run the accepted job')
135 }
136 ex.stop()
137 ran_after_stop := ex.run_one()!
138 if ran_after_stop {
139 return error('bounded timeout benchmark ran an unexpected job after stop')
140 }
141 ex.wait()!
142 }
143 return total
144}
145
146fn bench_many_producers(producers int) !int {
147 mut ex := executor.new(queue_size: producers)!
148 accepted := chan bool{cap: producers}
149 ran := chan int{cap: producers}
150 mut threads := []thread{}
151
152 for i in 0 .. producers {
153 threads << spawn fn [mut ex, accepted, ran, i] () {
154 ex.post_with_context(context.background(), fn [ran, i] () ! {
155 value := i + 1
156 ran <- value
157 return
158 }) or {
159 accepted <- false
160 return
161 }
162 accepted <- true
163 }()
164 }
165 for _ in 0 .. producers {
166 accepted_ok := <-accepted
167 if !accepted_ok {
168 return error('producer admission failed')
169 }
170 }
171 drained := ex.drain_pending(producers)!
172 if drained != producers {
173 return error('multi-producer benchmark drained ${drained} jobs instead of ${producers}')
174 }
175
176 mut total := 0
177 for _ in 0 .. producers {
178 total += <-ran
179 }
180 for producer_thread in threads {
181 producer_thread.wait()
182 }
183 ex.stop()
184 ran_after_stop := ex.run_one()!
185 if ran_after_stop {
186 return error('multi-producer benchmark ran an unexpected job after stop')
187 }
188 ex.wait()!
189 return total
190}
191