cheatah
Source

docs/gen-cheatah/gen_bench_parallel.purr

1# Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).
2# Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.
3# gen_bench_parallel.purr — the pure-cheatah doc-render benchmark, PARALLELIZED across threads over
4# shared memory.Owners. Same render WORK as gen_bench.purr (so the byte total matches, and the timing
5# is apples-to-apples — the only difference is threading). Showcases four stdlib modules cooperating:
6# parsers.xml — parse each Doxygen namespace XML (the from-scratch cheatah XML reader)
7# memory — ONE pinned Owner holds the read-only sources (coexisting shared read leases); TWO
8# Owners are the byte + function-count accumulators (exclusive write leases). The
9# drain-before-write engine makes both totals EXACT regardless of interleaving.
10# thread — thread.spawn runs the workers; each renders a slice of the modules.
11# regex — count the members that are functions (those with an argument list).
12# The byte + function totals are DETERMINISTIC (independent of thread count / interleaving).
13import io
14import os
15import time
16import string
17import parsers.xml
18import regex
19import memory
20import thread
22fn child_text(d, id, tag) { return parsers.xml.text(d, parsers.xml.find(d, id, tag)) }
24fn esc(s) {
25 let r = string.replace(s, "&", "&")
26 r = string.replace(r, "<", "&lt;")
27 return string.replace(r, ">", "&gt;")
30# Render sources [lo, hi) read from the SHARED owner (one parse pass each): accumulate the rendered
31# byte total AND — via regex — the count of function members, then publish both with exclusive writes.
32# The HTML built here is byte-identical to gen_bench.purr's render(), so the totals line up exactly.
33fn worker(srcs_o : memory.Owner<list<str>>, bytes_o : memory.Owner<int>,
34 fns_o : memory.Owner<int>, lo : int, hi : int) {
35 let re = regex.compile("\\([^)]*\\)") # each thread owns its DFA cache (no shared regex state)
36 let lb = 0
37 let lf = 0
38 with srcs_o.rread().acquire() as r { # one shared read lease covers this slice
39 let i = lo
40 while i < hi {
41 let d = parsers.xml.parse(r.read(i))
42 let root = parsers.xml.root(d)
43 for cd in parsers.xml.iter(d, root, "compounddef") {
44 lb = lb + len("<h1><code>" + esc(child_text(d, cd, "compoundname")) + "</code></h1>\n")
45 for md in parsers.xml.iter(d, cd, "memberdef") {
46 let args = child_text(d, md, "argsstring")
47 if regex.search(re, args) { lf = lf + 1 }
48 let html = "<section class=\"member\"><span class=\"badge\">"
49 html = html + parsers.xml.attr(d, md, "kind") + "</span> <code>"
50 html = html + esc(child_text(d, md, "name")) + esc(args)
51 html = html + "</code><p>" + esc(child_text(d, md, "briefdescription")) + "</p></section>\n"
52 lb = lb + len(html)
53 }
54 }
55 i = i + 1
56 }
57 }
58 with bytes_o.rwrite().acquire() as w { w.write(w.read() + lb) }
59 with fns_o.rwrite().acquire() as w { w.write(w.read() + lf) }
62fn main() {
63 let dir = "docs/xml"
64 let srcs: list<str> = []
65 for f in os.listdir(dir) {
66 if string.startswith(f, "namespacecheatah_") and string.endswith(f, ".xml") {
67 srcs.append(io.read_file(os.path.join(dir, f)))
68 }
69 }
70 let n = len(srcs)
71 let srcs_o = memory.own(srcs)
73 # ONE timed pass per invocation, matching gen_bench.purr and gen_bench.py so
74 # gen_bench_compare.purr can interleave all three. Every sample is printed; a best-of-N
75 # minimum hides exactly the dispersion a threading claim most needs to show.
76 let bytes = 0
77 let fns = 0
78 let q = n / 4
79 let k = 0
80 while k < 1 {
81 let bytes_o = memory.own(0)
82 let fns_o = memory.own(0)
83 let t0 = time.perf_counter()
84 with thread.spawn(worker, srcs_o, bytes_o, fns_o, 0, q) {
85 with thread.spawn(worker, srcs_o, bytes_o, fns_o, q, q + q) {
86 with thread.spawn(worker, srcs_o, bytes_o, fns_o, q + q, q + q + q) {
87 with thread.spawn(worker, srcs_o, bytes_o, fns_o, q + q + q, n) {
88 }
89 }
90 }
91 }
92 let dt = time.perf_counter() - t0
93 io.print("sample_ms=" + io.str(dt * 1000.0))
94 with bytes_o.rread().acquire() as r { bytes = r.read() }
95 with fns_o.rread().acquire() as r { fns = r.read() }
96 k = k + 1
97 }
98 io.print("cheatah-parallel files=" + io.str(n) + " out_bytes=" + io.str(bytes) + " fns=" + io.str(fns))
101main()