1package vt
2
3import (
4 "runtime"
5 "testing"
6
7 "github.com/bnema/vev-vt/core"
8)
9
10const (
11 storageBenchmarkRows = 10_000
12 storageBenchmarkWidth = 120
13)
14
15var benchmarkStorageHistorySink *History
16
17type storageBenchmarkScenario struct {
18 name string
19 fill func([]core.Cell, int)
20}
21
22func BenchmarkHistoryBuild10Kx120(b *testing.B) {
23 for _, scenario := range historyStorageScenarios() {
24 b.Run(scenario.name, func(b *testing.B) {
25 b.ReportAllocs()
26 for b.Loop() {
27 benchmarkStorageHistorySink = buildStorageBenchmarkHistory(b, scenario.fill)
28 }
29 b.ReportMetric(storageBenchmarkRows*storageBenchmarkWidth, "cells/op")
30 })
31 }
32}
33
34// BenchmarkHistoryRetained10Kx120 isolates each sample with garbage collection
35// and reports the live heap retained by one history. Use -benchtime=1x when
36// comparing representations; elapsed time includes the measurement GCs.
37func BenchmarkHistoryRetained10Kx120(b *testing.B) {
38 for _, scenario := range historyStorageScenarios() {
39 b.Run(scenario.name, func(b *testing.B) {
40 var retained uint64
41 for b.Loop() {
42 benchmarkStorageHistorySink = nil
43 runtime.GC()
44 var before runtime.MemStats
45 runtime.ReadMemStats(&before)
46
47 benchmarkStorageHistorySink = buildStorageBenchmarkHistory(b, scenario.fill)
48 runtime.GC()
49 var after runtime.MemStats
50 runtime.ReadMemStats(&after)
51 if after.HeapAlloc >= before.HeapAlloc {
52 retained += after.HeapAlloc - before.HeapAlloc
53 }
54 }
55 benchmarkStorageHistorySink = nil
56 if b.N > 0 {
57 b.ReportMetric(float64(retained)/float64(b.N), "retained-B/op")
58 }
59 })
60 }
61}
62
63// BenchmarkColdHistoryRetained10Kx120 measures the same scenarios after two
64// bounded idle passes. The newest sealed page and mutable tail remain hot.
65func BenchmarkColdHistoryRetained10Kx120(b *testing.B) {
66 for _, scenario := range historyStorageScenarios() {
67 b.Run(scenario.name, func(b *testing.B) {
68 var retained uint64
69 for b.Loop() {
70 benchmarkStorageHistorySink = nil
71 runtime.GC()
72 var before, after runtime.MemStats
73 runtime.ReadMemStats(&before)
74 h := buildStorageBenchmarkHistory(b, scenario.fill)
75 for range 2 {
76 if _, err := h.CompressIdle(len(h.chunks)); err != nil {
77 b.Fatal(err)
78 }
79 }
80 benchmarkStorageHistorySink = h
81 runtime.GC()
82 runtime.ReadMemStats(&after)
83 if after.HeapAlloc >= before.HeapAlloc {
84 retained += after.HeapAlloc - before.HeapAlloc
85 }
86 }
87 stats := benchmarkStorageHistorySink.CompressionStats()
88 b.ReportMetric(float64(stats.CompressedBytes), "compressed-B")
89 b.ReportMetric(float64(stats.ColdPages), "cold-pages")
90 b.ReportMetric(float64(retained)/float64(b.N), "retained-B/op")
91 benchmarkStorageHistorySink = nil
92 })
93 }
94}
95
96func historyStorageScenarios() []storageBenchmarkScenario {
97 defaultStyle := core.DefaultStyle()
98 repeatedIndexed := core.Style{
99 Bold: true,
100 Foreground: 196,
101 Background: 17,
102 UnderlineStyle: core.UnderlineSingle,
103 }
104 repeatedRGB := core.Style{
105 Foreground: -1,
106 Background: -1,
107 HasForegroundRGB: true,
108 ForegroundRGB: core.RGB{R: 224, G: 108, B: 117},
109 HasBackgroundRGB: true,
110 BackgroundRGB: core.RGB{R: 40, G: 44, B: 52},
111 }
112 return []storageBenchmarkScenario{
113 {
114 name: "plain-ascii",
115 fill: func(row []core.Cell, y int) {
116 for x := range row {
117 row[x] = core.Cell{Rune: rune('a' + (x+y)%26), Style: defaultStyle}
118 }
119 },
120 },
121 {
122 name: "repeated-indexed-style",
123 fill: func(row []core.Cell, y int) {
124 for x := range row {
125 row[x] = core.Cell{Rune: rune('a' + (x+y)%26), Style: repeatedIndexed}
126 }
127 },
128 },
129 {
130 name: "repeated-rgb-style",
131 fill: func(row []core.Cell, y int) {
132 for x := range row {
133 row[x] = core.Cell{Rune: rune('a' + (x+y)%26), Style: repeatedRGB}
134 }
135 },
136 },
137 {
138 name: "high-cardinality-rgb",
139 fill: func(row []core.Cell, y int) {
140 for x := range row {
141 value := uint32(y*len(row) + x)
142 style := core.Style{
143 Foreground: -1,
144 Background: -1,
145 HasForegroundRGB: true,
146 ForegroundRGB: core.RGB{
147 R: uint8(value >> 16),
148 G: uint8(value >> 8),
149 B: uint8(value),
150 },
151 }
152 row[x] = core.Cell{Rune: 'x', Style: style}
153 }
154 },
155 },
156 {
157 name: "wide-unicode",
158 fill: func(row []core.Cell, _ int) {
159 for x := 0; x < len(row); x += 2 {
160 row[x] = core.Cell{Rune: '界', Style: defaultStyle}
161 row[x+1] = core.Cell{Style: defaultStyle, Continuation: true}
162 }
163 },
164 },
165 {
166 name: "styled-blanks",
167 fill: func(row []core.Cell, y int) {
168 style := core.Style{Foreground: -1, Background: (y % 16) + 232}
169 for x := range row {
170 row[x] = core.Cell{Rune: ' ', Style: style}
171 }
172 },
173 },
174 }
175}
176
177func buildStorageBenchmarkHistory(b *testing.B, fill func([]core.Cell, int)) *History {
178 b.Helper()
179 history := NewHistory(HistoryConfig{
180 MaxRows: storageBenchmarkRows,
181 MaxBytes: 1 << 30,
182 ChunkRows: 256,
183 })
184 row := make([]core.Cell, storageBenchmarkWidth)
185 for y := range storageBenchmarkRows {
186 fill(row, y)
187 if err := history.Append(row, LineBound{End: len(row)}); err != nil {
188 b.Fatal(err)
189 }
190 }
191 return history
192}