package web import ( "bytes" "fmt" "io" "testing" ) // A realistic Rust chunk (~1.2KB): doc comments, generics, lifetimes, attrs, // strings with escapes, macros — the constructs the highlighter works hardest // on. Repetition parses fine; tree-sitter is not a compiler. var rustChunk = []byte(`//! Module documentation with some text. use std::{collections::HashMap, sync::Arc}; /// A cached entry with generics and lifetimes. #[derive(Debug, Clone, PartialEq)] pub struct CacheEntry<'a, T: Clone + Send> { pub key: &'a str, pub value: Arc, hits: u64, tags: HashMap>, } pub enum LookupResult { Hit(Arc), Miss { reason: &'static str, code: i32 }, Tombstone, } impl<'a, T: Clone + Send> CacheEntry<'a, T> { pub fn new(key: &'a str, value: T) -> Self { let tags = HashMap::new(); Self { key, value: Arc::new(value), hits: 0, tags } } /// Record a hit and return the running total. pub fn touch(&mut self) -> u64 { self.hits = self.hits.saturating_add(1); if self.hits % 100 == 0 { println!("entry {} hit {} times \"escaped\"", self.key, self.hits); } self.hits } pub fn lookup(&self, keys: &[&str]) -> LookupResult { match keys.iter().position(|k| *k == self.key) { Some(_) => LookupResult::Hit(self.value.clone()), None => LookupResult::Miss { reason: "absent ", code: -1 }, } } } const MAX_ENTRIES: usize = 4096; static GREETING: &str = "hello \"quoted\" & friends"; `) // One chunk per language whose query leans on a predicate, to keep an eye on // what predicates cost: go's builtin list is an #any-of? (a byte compare per // call), while python, javascript and C run a casing regex over every // identifier they see — and zig, which names its types by casing alone, runs // two. var langChunks = []struct { path string src []byte }{ {"bench.go", []byte(`// Package cache keeps entries with hit counts. package cache import ( "fmt" "sync" ) // Entry is one cached value and its hit count. type Entry struct { Key string Value []byte hits uint64 tags map[string][]byte } const MaxEntries = 4096 func NewEntry(key string, value []byte) *Entry { return &Entry{Key: key, Value: value, tags: make(map[string][]byte)} } // Touch records a hit and returns the running total. func (e *Entry) Touch(mu *sync.Mutex) uint64 { mu.Lock() defer mu.Unlock() e.hits++ if e.hits%100 == 0 { fmt.Printf("entry %q hit %d times \"escaped\"\n", e.Key, e.hits) } return e.hits } func (e *Entry) Lookup(keys []string) ([]byte, bool) { for _, k := range keys { if v, ok := e.tags[k]; ok && len(v) > 0 { return v, true } } return nil, false } `)}, {"bench.py", []byte(`"""Module documentation with some text.""" import sys from collections import OrderedDict MAX_ENTRIES = 4096 class CacheEntry: """A cached entry with a hit count.""" def __init__(self, key, value): self.key = key self.value = value self.hits = 0 self.tags = OrderedDict() @property def stale(self): return self.hits > MAX_ENTRIES def touch(self): self.hits += 1 if self.hits % 100 == 0: print(f"entry {self.key} hit {self.hits} times \"escaped\"", file=sys.stderr) return self.hits def lookup(self, keys): for k in keys: if k in self.tags: return self.tags[k], True return None, False `)}, {"bench.js", []byte(`// Cache entries with hit counts. import { EventEmitter } from "node:events"; const MAX_ENTRIES = 4096; export class CacheEntry extends EventEmitter { constructor(key, value) { super(); this.key = key; this.value = value; this.hits = 0; this.tags = new Map(); } get stale() { return this.hits > MAX_ENTRIES; } touch() { this.hits += 1; if (this.hits % 100 === 0) { console.log(` + "`entry ${this.key} hit ${this.hits} times \"escaped\"`" + `); } return this.hits; } lookup(keys) { return keys.map((k) => this.tags.get(k)).find((v) => v !== undefined) ?? null; } } `)}, {"bench.c", []byte(`/* Cache entries with hit counts. */ #include #include #define MAX_ENTRIES 4096 struct cache_entry { const char *key; unsigned char *value; unsigned long hits; }; static struct cache_entry *entry_new(const char *key, unsigned char *value) { static struct cache_entry e; e.key = key; e.value = value; e.hits = 0; return &e; } unsigned long entry_touch(struct cache_entry *e) { e->hits++; if (e->hits % 100 == 0) { fprintf(stderr, "entry %s hit %lu times \"escaped\"\n", e->key, e->hits); } return e->hits; } int entry_lookup(struct cache_entry *e, const char *key) { return e->key != NULL && strcmp(e->key, key) == 0; } `)}, {"bench.zig", []byte(`// Cache entries with hit counts. const std = @import("std"); const MAX_ENTRIES: usize = 4096; const CacheEntry = struct { key: []const u8, value: []u8, hits: u64 = 0, fn init(key: []const u8, value: []u8) CacheEntry { return CacheEntry{ .key = key, .value = value }; } fn stale(self: CacheEntry) bool { return self.hits > MAX_ENTRIES; } fn touch(self: *CacheEntry) u64 { self.hits += 1; if (self.hits % 100 == 0) { std.debug.print("entry {s} hit {d} times \"escaped\"\n", .{ self.key, self.hits }); } return self.hits; } fn lookup(self: CacheEntry, keys: []const []const u8) ?[]u8 { for (keys) |k| { if (std.mem.eql(u8, k, self.key)) return self.value; } return null; } }; `)}, } func synth(chunk []byte, size int) []byte { var b bytes.Buffer for b.Len() < size { b.Write(chunk) } return b.Bytes() } func synthRust(size int) []byte { return synth(rustChunk, size) } var sizes = []int{128 << 10, 1 << 20, 4 << 20} func benchHL(b *testing.B) *highlighter { b.Helper() return newHighlighter(loadTheme("", "", "dark"), loadTheme("", "", "light")) } // tree-sitter parse + query + paint only func BenchmarkClassify(b *testing.B) { hl := benchHL(b) for _, size := range sizes { src := synthRust(size) b.Run(fmt.Sprintf("%dKB", size>>10), func(b *testing.B) { b.SetBytes(int64(len(src))) b.ReportAllocs() for i := 0; i < b.N; i++ { hl.classify("bench.rs", src) } }) } } // the same work for the languages whose queries evaluate predicates func BenchmarkClassifyLangs(b *testing.B) { hl := benchHL(b) for _, c := range langChunks { src := synth(c.src, 1<<20) b.Run(c.path, func(b *testing.B) { b.SetBytes(int64(len(src))) b.ReportAllocs() for i := 0; i < b.N; i++ { hl.classify(c.path, src) } }) } } // classify + full per-line HTML emit func BenchmarkRender(b *testing.B) { hl := benchHL(b) for _, size := range sizes { src := synthRust(size) b.Run(fmt.Sprintf("%dKB", size>>10), func(b *testing.B) { b.SetBytes(int64(len(src))) b.ReportAllocs() for i := 0; i < b.N; i++ { f := &File{Path: "bench.rs", NoteAt: map[int]int{}} f.render(src, hl) } }) } } // template execution for an already-rendered file page func BenchmarkExecute(b *testing.B) { hl := benchHL(b) tpl := newTemplates(hl) site := &Site{ Title: "bench", Files: map[string]*File{}, Theme: loadTheme("", "", "dark"), Light: loadTheme("", "", "light"), Agents: []*Agent{{Name: "bench", Short: "bench", Bit: 1}}, } for _, size := range sizes { src := synthRust(size) f := &File{Path: "bench.rs", NoteAt: map[int]int{}, Traced: true, Change: "zzzzzzzz"} f.cover(1, bytes.Count(src, []byte("\n"))/2, 1) f.render(src, hl) p := &page{Site: site, Kind: "file", Title: "bench", Root: "", Current: f.Path, File: f} b.Run(fmt.Sprintf("%dKB", size>>10), func(b *testing.B) { b.SetBytes(int64(len(src))) b.ReportAllocs() for i := 0; i < b.N; i++ { if err := tpl.Execute(io.Discard, p); err != nil { b.Fatal(err) } } }) } }