1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
package main
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<T>,
hits: u64,
tags: HashMap<String, Vec<u8>>,
}
pub enum LookupResult<T> {
Hit(Arc<T>),
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<T> {
match keys.iter().position(|k| *k == self.key) {
Some(_) => LookupResult::Hit(self.value.clone()),
None => LookupResult::Miss { reason: "absent <key>", code: -1 },
}
}
}
const MAX_ENTRIES: usize = 4096;
static GREETING: &str = "hello \"quoted\" <world> & friends";
`)
func synthRust(size int) []byte {
var b bytes.Buffer
for b.Len() < size {
b.Write(rustChunk)
}
return b.Bytes()
}
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)
}
})
}
}
// 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()
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)
}
}
})
}
}
|