v2 / vlib / x / templating / dtm2 / benchmarks / dtm2_benchmark.v
212 lines · 193 sloc · 7.3 KB · 3eff1b83cf719199d0ff6f63524da63c9294ffd5
Raw
1module main
2
3import os
4import strings
5import time
6import x.templating.dtm2
7
8const bench_root_name = 'dtm2_benchmark'
9
10struct BenchConfig {
11 case_name string
12 iterations int
13 cold_iterations int
14 placeholder_count int
15 compress_html bool
16 reload_modified_templates bool
17 validate_each_iteration bool
18}
19
20fn main() {
21 config := BenchConfig{
22 case_name: os.getenv('DTM2_BENCH_CASE')
23 iterations: env_int('DTM2_BENCH_ITERATIONS', 50000)
24 cold_iterations: env_int('DTM2_BENCH_COLD_ITERATIONS', 500)
25 placeholder_count: env_int('DTM2_BENCH_PLACEHOLDERS', 50)
26 compress_html: env_bool('DTM2_BENCH_COMPRESS_HTML', true)
27 reload_modified_templates: env_bool('DTM2_BENCH_RELOAD_MODIFIED_TEMPLATES', false)
28 validate_each_iteration: env_bool('DTM2_BENCH_VALIDATE_EACH_ITERATION', false)
29 }
30 root := os.join_path(os.vtmp_dir(), '${bench_root_name}_${os.getpid()}')
31 os.rmdir_all(root) or {}
32 setup_files(root, config)!
33 defer {
34 os.rmdir_all(root) or {}
35 }
36
37 print_config(root, config)
38 small := small_placeholders()
39 many := many_placeholders(config.placeholder_count)
40 include := include_placeholders()
41
42 if should_run(config, 'small_hot') {
43 bench_hot('small_hot', root, 'small.html', small, config.iterations, config)
44 }
45 if should_run(config, 'small_cold') {
46 bench_cold('small_cold', root, 'small.html', small, config.cold_iterations, config)
47 }
48 if should_run(config, 'many_hot') {
49 bench_hot('many_hot', root, 'many.html', many, config.iterations, config)
50 }
51 if should_run(config, 'many_cold') {
52 bench_cold('many_cold', root, 'many.html', many, config.cold_iterations, config)
53 }
54 if should_run(config, 'include_hot') {
55 bench_hot('include_hot', root, 'with_include.html', include, config.iterations, config)
56 }
57 if should_run(config, 'include_cold') {
58 bench_cold('include_cold', root, 'with_include.html', include, config.cold_iterations,
59 config)
60 }
61 if should_run(config, 'xml_hot') {
62 bench_hot('xml_hot', root, 'feed.xml', small, config.iterations, config)
63 }
64 if should_run(config, 'xml_cold') {
65 bench_cold('xml_cold', root, 'feed.xml', small, config.cold_iterations, config)
66 }
67}
68
69fn print_config(root string, config BenchConfig) {
70 println('DTM2 benchmark')
71 println('root: ${root}')
72 println('case_name: ${if config.case_name == '' { 'all' } else { config.case_name }}')
73 println('iterations: ${config.iterations}')
74 println('cold_iterations: ${config.cold_iterations}')
75 println('placeholder_count: ${config.placeholder_count}')
76 println('compress_html: ${config.compress_html}')
77 println('reload_modified_templates: ${config.reload_modified_templates}')
78 println('validate_each_iteration: ${config.validate_each_iteration}')
79 println('')
80}
81
82fn env_int(name string, default_value int) int {
83 raw := os.getenv(name)
84 if raw == '' {
85 return default_value
86 }
87 value := raw.int()
88 if value <= 0 {
89 return default_value
90 }
91 return value
92}
93
94fn env_bool(name string, default_value bool) bool {
95 raw := os.getenv(name).to_lower()
96 if raw == '' {
97 return default_value
98 }
99 if raw in ['1', 'true', 'yes', 'on'] {
100 return true
101 }
102 if raw in ['0', 'false', 'no', 'off'] {
103 return false
104 }
105 return default_value
106}
107
108fn should_run(config BenchConfig, label string) bool {
109 return config.case_name == '' || config.case_name == 'all' || config.case_name == label
110}
111
112fn setup_files(root string, config BenchConfig) ! {
113 os.mkdir_all(os.join_path(root, 'partials'))!
114 os.write_file(os.join_path(root, 'small.html'),
115 '<main>@title <p>@body</p><span>@count</span></main>')!
116 mut many := strings.new_builder(config.placeholder_count * 30)
117 many.writeln('<dl>')
118 for i := 0; i < config.placeholder_count; i++ {
119 many.writeln('<dt>${i}</dt><dd>@p_${i}</dd>')
120 }
121 many.writeln('</dl>')
122 os.write_file(os.join_path(root, 'many.html'), many.str())!
123 os.write_file(os.join_path(root, 'partials', 'nav.html'), '<nav>@title</nav>')!
124 os.write_file(os.join_path(root, 'with_include.html'),
125 '<header>@include "partials/nav"</header><main>@body</main>')!
126 os.write_file(os.join_path(root, 'feed.xml'),
127 '<feed><title>@title</title><entry>@body</entry><count>@count</count></feed>')!
128}
129
130fn small_placeholders() map[string]string {
131 mut placeholders := map[string]string{}
132 placeholders['title'] = 'Small'.clone()
133 placeholders['body'] = 'Body <escaped>'.clone()
134 placeholders['count'] = '42'
135 return placeholders
136}
137
138fn many_placeholders(count int) map[string]string {
139 mut placeholders := map[string]string{}
140 for i := 0; i < count; i++ {
141 placeholders['p_${i}'] = 'value ${i}'.clone()
142 }
143 return placeholders
144}
145
146fn include_placeholders() map[string]string {
147 mut placeholders := map[string]string{}
148 placeholders['title'] = 'Menu'.clone()
149 placeholders['body'] = 'Body <escaped>'.clone()
150 return placeholders
151}
152
153fn bench_hot(label string, root string, template_path string, placeholders map[string]string, iterations int, config BenchConfig) {
154 mut manager := new_manager(root, config)
155 expected := manager.expand(template_path, placeholders: &placeholders).clone()
156 mut last_len := expected.len
157 sw := time.new_stopwatch()
158 for i in 0 .. iterations {
159 rendered := manager.expand(template_path, placeholders: &placeholders)
160 if config.validate_each_iteration {
161 validate_rendered(label, i, expected, rendered)
162 }
163 last_len = rendered.len
164 }
165 elapsed := sw.elapsed()
166 validate_rendered(label, iterations, expected, manager.expand(template_path,
167 placeholders: &placeholders
168 ))
169 print_result(label, iterations, elapsed, last_len, manager.compiled_template_count())
170}
171
172fn bench_cold(label string, root string, template_path string, placeholders map[string]string, iterations int, config BenchConfig) {
173 mut expected_manager := new_manager(root, config)
174 expected := expected_manager.expand(template_path, placeholders: &placeholders).clone()
175 mut last_len := expected.len
176 sw := time.new_stopwatch()
177 for i in 0 .. iterations {
178 mut manager := new_manager(root, config)
179 rendered := manager.expand(template_path, placeholders: &placeholders)
180 if config.validate_each_iteration {
181 validate_rendered(label, i, expected, rendered)
182 }
183 last_len = rendered.len
184 }
185 elapsed := sw.elapsed()
186 mut final_manager := new_manager(root, config)
187 validate_rendered(label, iterations, expected, final_manager.expand(template_path,
188 placeholders: &placeholders
189 ))
190 print_result(label, iterations, elapsed, last_len, final_manager.compiled_template_count())
191}
192
193fn new_manager(root string, config BenchConfig) &dtm2.Manager {
194 return dtm2.initialize(
195 template_dir: root
196 compress_html: config.compress_html
197 reload_modified_templates: config.reload_modified_templates
198 )
199}
200
201fn validate_rendered(label string, iteration int, expected string, rendered string) {
202 if rendered != expected {
203 eprintln('${label} invalid output at iteration ${iteration}: expected_len=${expected.len} actual_len=${rendered.len}')
204 exit(1)
205 }
206}
207
208fn print_result(label string, iterations int, elapsed time.Duration, last_len int, compiled_count int) {
209 ns_per_op := if iterations > 0 { elapsed.nanoseconds() / i64(iterations) } else { i64(0) }
210 ops_per_sec := if ns_per_op > 0 { 1_000_000_000.0 / f64(ns_per_op) } else { 0.0 }
211 println('${label:-16} iterations=${iterations:8} total_ms=${elapsed.milliseconds():8} ns_per_op=${ns_per_op:10} ops_per_sec=${ops_per_sec:10.1f} last_len=${last_len:6} compiled_templates=${compiled_count}')
212}
213