Skip to main content

max / makenotwork

docengine: add criterion render-path benchmarks Baseline for the per-request render path and startup doc-load before any perf refactor (parser reuse, Arc<str>). Four groups: Renderer::render across the four presets on a ~2KB description, render on a ~20KB page with directives + images, DocLoader::load on the site-docs corpus, and post_process_directives / post_process_quotes separately. Gated on doc-loader/directives/quotes; run with cargo bench --features full. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-23 13:51 UTC
Commit: c024e7e7d719a777b5f2a69939e02b176e23313e
Parent: 662f514
3 files changed, +566 insertions, -0 deletions
@@ -3,6 +3,24 @@
3 3 version = 4
4 4
5 5 [[package]]
6 + name = "aho-corasick"
7 + version = "1.1.4"
8 + source = "registry+https://github.com/rust-lang/crates.io-index"
9 + checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
10 + dependencies = [
11 + "memchr",
12 + ]
13 +
14 + [[package]]
15 + name = "alloca"
16 + version = "0.4.0"
17 + source = "registry+https://github.com/rust-lang/crates.io-index"
18 + checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
19 + dependencies = [
20 + "cc",
21 + ]
22 +
23 + [[package]]
6 24 name = "ammonia"
7 25 version = "4.1.4"
8 26 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -15,12 +33,30 @@ dependencies = [
15 33 ]
16 34
17 35 [[package]]
36 + name = "anes"
37 + version = "0.1.6"
38 + source = "registry+https://github.com/rust-lang/crates.io-index"
39 + checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
40 +
41 + [[package]]
42 + name = "anstyle"
43 + version = "1.0.14"
44 + source = "registry+https://github.com/rust-lang/crates.io-index"
45 + checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
46 +
47 + [[package]]
18 48 name = "anyhow"
19 49 version = "1.0.104"
20 50 source = "registry+https://github.com/rust-lang/crates.io-index"
21 51 checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
22 52
23 53 [[package]]
54 + name = "autocfg"
55 + version = "1.5.1"
56 + source = "registry+https://github.com/rust-lang/crates.io-index"
57 + checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
58 +
59 + [[package]]
24 60 name = "bitflags"
25 61 version = "2.11.0"
26 62 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -33,12 +69,146 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
33 69 checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
34 70
35 71 [[package]]
72 + name = "cast"
73 + version = "0.3.0"
74 + source = "registry+https://github.com/rust-lang/crates.io-index"
75 + checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
76 +
77 + [[package]]
78 + name = "cc"
79 + version = "1.3.0"
80 + source = "registry+https://github.com/rust-lang/crates.io-index"
81 + checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8"
82 + dependencies = [
83 + "find-msvc-tools",
84 + "shlex",
85 + ]
86 +
87 + [[package]]
36 88 name = "cfg-if"
37 89 version = "1.0.4"
38 90 source = "registry+https://github.com/rust-lang/crates.io-index"
39 91 checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
40 92
41 93 [[package]]
94 + name = "ciborium"
95 + version = "0.2.2"
96 + source = "registry+https://github.com/rust-lang/crates.io-index"
97 + checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
98 + dependencies = [
99 + "ciborium-io",
100 + "ciborium-ll",
101 + "serde",
102 + ]
103 +
104 + [[package]]
105 + name = "ciborium-io"
106 + version = "0.2.2"
107 + source = "registry+https://github.com/rust-lang/crates.io-index"
108 + checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
109 +
110 + [[package]]
111 + name = "ciborium-ll"
112 + version = "0.2.2"
113 + source = "registry+https://github.com/rust-lang/crates.io-index"
114 + checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
115 + dependencies = [
116 + "ciborium-io",
117 + "half",
118 + ]
119 +
120 + [[package]]
121 + name = "clap"
122 + version = "4.6.4"
123 + source = "registry+https://github.com/rust-lang/crates.io-index"
124 + checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7"
125 + dependencies = [
126 + "clap_builder",
127 + ]
128 +
129 + [[package]]
130 + name = "clap_builder"
131 + version = "4.6.2"
132 + source = "registry+https://github.com/rust-lang/crates.io-index"
133 + checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
134 + dependencies = [
135 + "anstyle",
136 + "clap_lex",
137 + ]
138 +
139 + [[package]]
140 + name = "clap_lex"
141 + version = "1.1.0"
142 + source = "registry+https://github.com/rust-lang/crates.io-index"
143 + checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
144 +
145 + [[package]]
146 + name = "criterion"
147 + version = "0.8.2"
148 + source = "registry+https://github.com/rust-lang/crates.io-index"
149 + checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3"
150 + dependencies = [
151 + "alloca",
152 + "anes",
153 + "cast",
154 + "ciborium",
155 + "clap",
156 + "criterion-plot",
157 + "itertools",
158 + "num-traits",
159 + "oorandom",
160 + "page_size",
161 + "plotters",
162 + "rayon",
163 + "regex",
164 + "serde",
165 + "serde_json",
166 + "tinytemplate",
167 + "walkdir",
168 + ]
169 +
170 + [[package]]
171 + name = "criterion-plot"
172 + version = "0.8.2"
173 + source = "registry+https://github.com/rust-lang/crates.io-index"
174 + checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea"
175 + dependencies = [
176 + "cast",
177 + "itertools",
178 + ]
179 +
180 + [[package]]
181 + name = "crossbeam-deque"
182 + version = "0.8.7"
183 + source = "registry+https://github.com/rust-lang/crates.io-index"
184 + checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
185 + dependencies = [
186 + "crossbeam-epoch",
187 + "crossbeam-utils",
188 + ]
189 +
190 + [[package]]
191 + name = "crossbeam-epoch"
192 + version = "0.9.20"
193 + source = "registry+https://github.com/rust-lang/crates.io-index"
194 + checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
195 + dependencies = [
196 + "crossbeam-utils",
197 + ]
198 +
199 + [[package]]
200 + name = "crossbeam-utils"
201 + version = "0.8.22"
202 + source = "registry+https://github.com/rust-lang/crates.io-index"
203 + checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
204 +
205 + [[package]]
206 + name = "crunchy"
207 + version = "0.2.4"
208 + source = "registry+https://github.com/rust-lang/crates.io-index"
209 + checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
210 +
211 + [[package]]
42 212 name = "cssparser"
43 213 version = "0.37.0"
44 214 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -65,6 +235,7 @@ name = "docengine"
65 235 version = "0.3.5"
66 236 dependencies = [
67 237 "ammonia",
238 + "criterion",
68 239 "pulldown-cmark",
69 240 "regex-lite",
70 241 "serde",
@@ -90,6 +261,12 @@ dependencies = [
90 261 ]
91 262
92 263 [[package]]
264 + name = "either"
265 + version = "1.16.0"
266 + source = "registry+https://github.com/rust-lang/crates.io-index"
267 + checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
268 +
269 + [[package]]
93 270 name = "equivalent"
94 271 version = "1.0.2"
95 272 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -112,6 +289,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
112 289 checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
113 290
114 291 [[package]]
292 + name = "find-msvc-tools"
293 + version = "0.1.9"
294 + source = "registry+https://github.com/rust-lang/crates.io-index"
295 + checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
296 +
297 + [[package]]
115 298 name = "foldhash"
116 299 version = "0.1.5"
117 300 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -149,6 +332,17 @@ dependencies = [
149 332 ]
150 333
151 334 [[package]]
335 + name = "half"
336 + version = "2.7.1"
337 + source = "registry+https://github.com/rust-lang/crates.io-index"
338 + checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
339 + dependencies = [
340 + "cfg-if",
341 + "crunchy",
342 + "zerocopy",
343 + ]
344 +
345 + [[package]]
152 346 name = "hashbrown"
153 347 version = "0.15.5"
154 348 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -300,6 +494,15 @@ dependencies = [
300 494 ]
301 495
302 496 [[package]]
497 + name = "itertools"
498 + version = "0.13.0"
499 + source = "registry+https://github.com/rust-lang/crates.io-index"
500 + checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
501 + dependencies = [
502 + "either",
503 + ]
504 +
505 + [[package]]
303 506 name = "itoa"
304 507 version = "1.0.18"
305 508 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -384,12 +587,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
384 587 checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
385 588
386 589 [[package]]
590 + name = "num-traits"
591 + version = "0.2.19"
592 + source = "registry+https://github.com/rust-lang/crates.io-index"
593 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
594 + dependencies = [
595 + "autocfg",
596 + ]
597 +
598 + [[package]]
387 599 name = "once_cell"
388 600 version = "1.21.4"
389 601 source = "registry+https://github.com/rust-lang/crates.io-index"
390 602 checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
391 603
392 604 [[package]]
605 + name = "oorandom"
606 + version = "11.1.5"
607 + source = "registry+https://github.com/rust-lang/crates.io-index"
608 + checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
609 +
610 + [[package]]
611 + name = "page_size"
612 + version = "0.6.0"
613 + source = "registry+https://github.com/rust-lang/crates.io-index"
614 + checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
615 + dependencies = [
616 + "libc",
617 + "winapi",
618 + ]
619 +
620 + [[package]]
393 621 name = "parking_lot"
394 622 version = "0.12.5"
395 623 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -464,6 +692,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
464 692 checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
465 693
466 694 [[package]]
695 + name = "plotters"
696 + version = "0.3.7"
697 + source = "registry+https://github.com/rust-lang/crates.io-index"
698 + checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
699 + dependencies = [
700 + "num-traits",
701 + "plotters-backend",
702 + "plotters-svg",
703 + "wasm-bindgen",
704 + "web-sys",
705 + ]
706 +
707 + [[package]]
708 + name = "plotters-backend"
709 + version = "0.3.7"
710 + source = "registry+https://github.com/rust-lang/crates.io-index"
711 + checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
712 +
713 + [[package]]
714 + name = "plotters-svg"
715 + version = "0.3.7"
716 + source = "registry+https://github.com/rust-lang/crates.io-index"
717 + checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
718 + dependencies = [
719 + "plotters-backend",
720 + ]
721 +
722 + [[package]]
467 723 name = "potential_utf"
468 724 version = "0.1.4"
469 725 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -532,6 +788,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
532 788 checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
533 789
534 790 [[package]]
791 + name = "rayon"
792 + version = "1.12.0"
793 + source = "registry+https://github.com/rust-lang/crates.io-index"
794 + checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
795 + dependencies = [
796 + "either",
797 + "rayon-core",
798 + ]
799 +
800 + [[package]]
801 + name = "rayon-core"
802 + version = "1.13.0"
803 + source = "registry+https://github.com/rust-lang/crates.io-index"
804 + checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
805 + dependencies = [
806 + "crossbeam-deque",
807 + "crossbeam-utils",
808 + ]
809 +
810 + [[package]]
535 811 name = "redox_syscall"
536 812 version = "0.5.18"
537 813 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -541,12 +817,41 @@ dependencies = [
541 817 ]
542 818
543 819 [[package]]
820 + name = "regex"
821 + version = "1.13.1"
822 + source = "registry+https://github.com/rust-lang/crates.io-index"
823 + checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
824 + dependencies = [
825 + "aho-corasick",
826 + "memchr",
827 + "regex-automata",
828 + "regex-syntax",
829 + ]
830 +
831 + [[package]]
832 + name = "regex-automata"
833 + version = "0.4.16"
834 + source = "registry+https://github.com/rust-lang/crates.io-index"
835 + checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
836 + dependencies = [
837 + "aho-corasick",
838 + "memchr",
839 + "regex-syntax",
840 + ]
841 +
842 + [[package]]
544 843 name = "regex-lite"
545 844 version = "0.1.9"
546 845 source = "registry+https://github.com/rust-lang/crates.io-index"
547 846 checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
548 847
549 848 [[package]]
849 + name = "regex-syntax"
850 + version = "0.8.11"
851 + source = "registry+https://github.com/rust-lang/crates.io-index"
852 + checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
853 +
854 + [[package]]
550 855 name = "rustix"
551 856 version = "1.1.4"
552 857 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -566,6 +871,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
566 871 checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
567 872
568 873 [[package]]
874 + name = "same-file"
875 + version = "1.0.6"
876 + source = "registry+https://github.com/rust-lang/crates.io-index"
877 + checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
878 + dependencies = [
879 + "winapi-util",
880 + ]
881 +
882 + [[package]]
569 883 name = "scopeguard"
570 884 version = "1.2.0"
571 885 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -630,6 +944,12 @@ dependencies = [
630 944 ]
631 945
632 946 [[package]]
947 + name = "shlex"
948 + version = "2.0.1"
949 + source = "registry+https://github.com/rust-lang/crates.io-index"
950 + checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
951 +
952 + [[package]]
633 953 name = "siphasher"
634 954 version = "1.0.2"
635 955 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -726,6 +1046,16 @@ dependencies = [
726 1046 ]
727 1047
728 1048 [[package]]
1049 + name = "tinytemplate"
1050 + version = "1.2.1"
1051 + source = "registry+https://github.com/rust-lang/crates.io-index"
1052 + checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
1053 + dependencies = [
1054 + "serde",
1055 + "serde_json",
1056 + ]
1057 +
1058 + [[package]]
729 1059 name = "toml"
730 1060 version = "1.1.3+spec-1.1.0"
731 1061 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -850,6 +1180,16 @@ dependencies = [
850 1180 ]
851 1181
852 1182 [[package]]
1183 + name = "walkdir"
1184 + version = "2.5.0"
1185 + source = "registry+https://github.com/rust-lang/crates.io-index"
1186 + checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
1187 + dependencies = [
1188 + "same-file",
1189 + "winapi-util",
1190 + ]
1191 +
1192 + [[package]]
853 1193 name = "wasip2"
854 1194 version = "1.0.2+wasi-0.2.9"
855 1195 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -947,6 +1287,16 @@ dependencies = [
947 1287 ]
948 1288
949 1289 [[package]]
1290 + name = "web-sys"
1291 + version = "0.3.91"
1292 + source = "registry+https://github.com/rust-lang/crates.io-index"
1293 + checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
1294 + dependencies = [
1295 + "js-sys",
1296 + "wasm-bindgen",
1297 + ]
1298 +
1299 + [[package]]
950 1300 name = "web_atoms"
951 1301 version = "0.2.5"
952 1302 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -959,6 +1309,37 @@ dependencies = [
959 1309 ]
960 1310
961 1311 [[package]]
1312 + name = "winapi"
1313 + version = "0.3.9"
1314 + source = "registry+https://github.com/rust-lang/crates.io-index"
1315 + checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1316 + dependencies = [
1317 + "winapi-i686-pc-windows-gnu",
1318 + "winapi-x86_64-pc-windows-gnu",
1319 + ]
1320 +
1321 + [[package]]
1322 + name = "winapi-i686-pc-windows-gnu"
1323 + version = "0.4.0"
1324 + source = "registry+https://github.com/rust-lang/crates.io-index"
1325 + checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1326 +
1327 + [[package]]
1328 + name = "winapi-util"
1329 + version = "0.1.11"
1330 + source = "registry+https://github.com/rust-lang/crates.io-index"
1331 + checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
1332 + dependencies = [
Lines truncated
@@ -27,11 +27,20 @@ tracing = { version = "0.1", optional = true }
27 27
28 28 [dev-dependencies]
29 29 tempfile = "3"
30 + criterion = { version = "0.8", features = ["html_reports"] }
30 31
31 32 [[bin]]
32 33 name = "substitute_dir"
33 34 required-features = ["assumptions"]
34 35
36 + # Run with: cargo bench --features full
37 + # (the render-path benchmarks exercise the doc-loader, directives, and quotes
38 + # post-processors, all feature-gated.)
39 + [[bench]]
40 + name = "render"
41 + harness = false
42 + required-features = ["doc-loader", "directives", "quotes"]
43 +
35 44 [lints.rust]
36 45 unused = "warn"
37 46 unreachable_pub = "warn"
@@ -0,0 +1,186 @@
1 + //! Render-path benchmarks.
2 + //!
3 + //! docengine sits on the MNW per-request render path (creator descriptions,
4 + //! forum posts) and the startup doc-load path (site-docs). These establish a
5 + //! baseline to measure any perf refactor against — parser reuse, `Arc<str>`
6 + //! sharing, or a directive pass that avoids re-scanning the whole HTML.
7 + //!
8 + //! Run with: `cargo bench --features full`.
9 +
10 + use std::collections::HashMap;
11 + use std::hint::black_box;
12 + use std::path::PathBuf;
13 +
14 + use criterion::{Criterion, criterion_group, criterion_main};
15 +
16 + use docengine::{
17 + DocLoader, DocLoaderConfig, QuoteAuthor, Renderer, post_process_directives, post_process_quotes,
18 + };
19 +
20 + /// A ~2 KB markdown description, the shape of a typical creator project blurb or
21 + /// a short forum post: headings, emphasis, a link, a list, a code span, a table.
22 + fn description_2kb() -> String {
23 + let block = "\
24 + ## What this is
25 +
26 + A small, focused tool for **doing one thing well**. It stays out of your way
27 + and does not phone home. See the [guide](/docs/guide/overview) for the full
28 + tour, or jump straight to `quickstart` below.
29 +
30 + - Fast, native builds — no runtime to install
31 + - Full export, no lock-in
32 + - Works offline first, syncs when you want it to
33 +
34 + | Tier | Price | Files |
35 + |------|-------|-------|
36 + | Basic | $16 | text |
37 + | Big Files | $36 | large |
38 +
39 + > A short aside about why this exists and who it is for.
40 + ";
41 + // The block is ~520 bytes; four copies lands near 2 KB.
42 + let mut s = String::with_capacity(2200);
43 + for _ in 0..4 {
44 + s.push_str(block);
45 + }
46 + s
47 + }
48 +
49 + /// A ~20 KB long-form doc page: many sections, alert directives, and images —
50 + /// the heavy end of what a single site-docs page renders to.
51 + fn doc_page_20kb() -> String {
52 + let section = "\
53 + ## Section heading
54 +
55 + Longer explanatory prose that runs a few sentences so the parser has real text
56 + to walk, not just markup. It references [another page](/docs/guide/other) and
57 + uses `inline code` plus **bold** and *italic* spans throughout the paragraph.
58 +
59 + ![a diagram](https://cdn.makenot.work/img/diagram.png)
60 +
61 + > [!NOTE]
62 + > A callout that the directives post-processor will rewrite into a styled alert
63 + > block. These are common in the developer docs.
64 +
65 + > [!WARNING]
66 + > A second callout of a different type, to exercise the type-matching arm.
67 +
68 + 1. First ordered step with some detail
69 + 2. Second step that continues the thought
70 + 3. Third step to close it out
71 + ";
72 + // The section is ~640 bytes; ~32 copies lands near 20 KB.
73 + let mut s = String::with_capacity(21_000);
74 + for _ in 0..32 {
75 + s.push_str(section);
76 + }
77 + s
78 + }
79 +
80 + /// Locate the live site-docs corpus relative to this crate. Returns `None` when
81 + /// the MNW server tree is not checked out alongside docengine (e.g. the crate
82 + /// was vendored standalone), so the load benchmark self-skips instead of failing.
83 + fn site_docs_path() -> Option<PathBuf> {
84 + let p = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../server/site-docs/public");
85 + p.is_dir().then_some(p)
86 + }
87 +
88 + fn server_doc_config() -> DocLoaderConfig {
89 + // Mirrors the sections the MNW server registers in main.rs. pre_process is
90 + // left None: assumption substitution is a separate cost, benched elsewhere.
91 + DocLoaderConfig {
92 + sections: vec![
93 + ("about".to_string(), "About".to_string()),
94 + ("guide".to_string(), "Guide".to_string()),
95 + ("developer".to_string(), "Developer".to_string()),
96 + ("legal".to_string(), "Legal".to_string()),
97 + ("support".to_string(), "Support".to_string()),
98 + ("tech".to_string(), "Tech".to_string()),
99 + ],
100 + link_prefix: "/docs".to_string(),
101 + unpublished_pattern: Some("unpublished/".to_string()),
102 + examples_path: None,
103 + pre_process: None,
104 + }
105 + }
106 +
107 + // (1) Renderer::render across the four presets on a ~2 KB description.
108 + fn bench_presets(c: &mut Criterion) {
109 + let input = description_2kb();
110 + let mut group = c.benchmark_group("render_presets_2kb");
111 + for (name, renderer) in [
112 + ("permissive", Renderer::permissive()),
113 + ("standard", Renderer::standard()),
114 + ("strict", Renderer::strict()),
115 + ("sanitize_only", Renderer::sanitize_only()),
116 + ] {
117 + group.bench_function(name, |b| {
118 + b.iter(|| renderer.render(black_box(&input)));
119 + });
120 + }
121 + group.finish();
122 + }
123 +
124 + // (2) render on a large ~20 KB doc page with directives + images (permissive,
125 + // the preset site-docs pages render under).
126 + fn bench_large_page(c: &mut Criterion) {
127 + let input = doc_page_20kb();
128 + c.bench_function("render_permissive_20kb_page", |b| {
129 + let renderer = Renderer::permissive();
130 + b.iter(|| renderer.render(black_box(&input)));
131 + });
132 + }
133 +
134 + // (3) DocLoader::load startup on the current server/site-docs corpus.
135 + fn bench_doc_loader(c: &mut Criterion) {
136 + let Some(path) = site_docs_path() else {
137 + eprintln!("skipping doc_loader_load: site-docs corpus not found alongside crate");
138 + return;
139 + };
140 + c.bench_function("doc_loader_load_site_docs", |b| {
141 + b.iter(|| DocLoader::load(black_box(&path), &server_doc_config()));
142 + });
143 + }
144 +
145 + // (4) post_process_directives + post_process_quotes, separately, on
146 + // already-rendered HTML (both run after render() on the request path).
147 + fn bench_post_process(c: &mut Criterion) {
148 + // Directives operate on rendered HTML, so render the alert-heavy page first.
149 + let directive_html = Renderer::permissive().render(&doc_page_20kb());
150 + c.bench_function("post_process_directives_20kb", |b| {
151 + b.iter(|| post_process_directives(black_box(&directive_html)));
152 + });
153 +
154 + // Quotes replace [quote:UUID:HASH] markers with author attribution. Build a
155 + // page carrying several markers and a matching author map.
156 + let id = uuid::Uuid::from_u128(0x0123_4567_89ab_cdef_0123_4567_89ab_cdef);
157 + let marker = format!("[quote:{id}:0123abcd]");
158 + let mut quote_html = String::with_capacity(4096);
159 + for _ in 0..16 {
160 + quote_html.push_str("<p>Some preceding paragraph text for context.</p>\n");
161 + quote_html.push_str("<blockquote><p>");
162 + quote_html.push_str(&marker);
163 + quote_html.push_str("</p></blockquote>\n");
164 + }
165 + let mut authors = HashMap::new();
166 + authors.insert(
167 + id,
168 + QuoteAuthor {
169 + username: "creator".to_string(),
170 + display_name: "A Creator".to_string(),
171 + is_removed: false,
172 + },
173 + );
174 + c.bench_function("post_process_quotes_16_markers", |b| {
175 + b.iter(|| post_process_quotes(black_box(&quote_html), black_box(&authors)));
176 + });
177 + }
178 +
179 + criterion_group!(
180 + benches,
181 + bench_presets,
182 + bench_large_page,
183 + bench_doc_loader,
184 + bench_post_process
185 + );
186 + criterion_main!(benches);