Performance goal, regression gate #5
@@ -360,6 +360,9 @@ tokens add cost in proportion to the output.
|
||||
7. **Zero dependencies** — standard library only.
|
||||
8. **Docs index the grammar** — every syntax feature is a heading; every example
|
||||
runs under test and shows its output; a rule is stated once.
|
||||
9. **Fast enough to be free** — a value renders in about a microsecond and `New`
|
||||
parses and validates the whole set once upfront, so generating fixtures stays
|
||||
noise against a test's own runtime.
|
||||
|
||||
## Decisions
|
||||
|
||||
@@ -451,6 +454,12 @@ tokens add cost in proportion to the output.
|
||||
- **Samples say what they emit, transforms what they do.** `{upper(2)}` is two
|
||||
letters, `{uppercase(x)}` is `x` upper-cased; one name for both would turn on
|
||||
whether the argument looks like a number.
|
||||
- **The performance gate asserts allocations, not wall-clock time.** `AllocsPerRun`
|
||||
is deterministic across machines, so a ±10% ceiling does not flake under CI load,
|
||||
while time varies with the machine and its neighbours. A rendering slowdown
|
||||
almost always costs an allocation too (a lost pre-size, a per-item map, an extra
|
||||
copy). The benchmark suite (see Development) reports time for a human, not as a
|
||||
pass/fail gate.
|
||||
|
||||
## Development
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package fejkdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
)
|
||||
|
||||
// One Fake call descends depth levels: each level's "a" is the next template down.
|
||||
func nestedJSON(depth int) string {
|
||||
s := `"leaf"`
|
||||
for i := 0; i < depth; i++ {
|
||||
s = fmt.Sprintf(`{"format":"{a}","a":%s}`, s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// One Fake call expands n sibling tokens.
|
||||
func wideTokenJSON(n int) string {
|
||||
var toks, fields strings.Builder
|
||||
for i := 0; i < n; i++ {
|
||||
fmt.Fprintf(&toks, "{f%d}", i)
|
||||
if i > 0 {
|
||||
fields.WriteByte(',')
|
||||
}
|
||||
fmt.Fprintf(&fields, `"f%d":"x"`, i)
|
||||
}
|
||||
return fmt.Sprintf(`{"format":"%s",%s}`, toks.String(), fields.String())
|
||||
}
|
||||
|
||||
func TestNoRenderAllocRegression(t *testing.T) {
|
||||
shapes := []struct {
|
||||
name string
|
||||
json string
|
||||
base float64
|
||||
}{
|
||||
{"nested depth 25", nestedJSON(25), 27},
|
||||
{"nested depth 100", nestedJSON(100), 102},
|
||||
{"wide 500 tokens", wideTokenJSON(500), 9},
|
||||
}
|
||||
for _, s := range shapes {
|
||||
f, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{"x.json": {Data: []byte(s.json)}}))
|
||||
if err != nil {
|
||||
t.Fatalf("New(%s): %v", s.name, err)
|
||||
}
|
||||
allocs := testing.AllocsPerRun(10000, func() { f.Fake("x") })
|
||||
if allocs > s.base*1.10 {
|
||||
t.Errorf("%s: %.1f allocs/op regressed past %.1f (baseline %.1f + 10%%); bump the baseline only as a deliberate change", s.name, allocs, s.base*1.10, s.base)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNestedDepth25(b *testing.B) { benchPath(b, tmpData(b, "deep", nestedJSON(25)), "deep") }
|
||||
func BenchmarkNestedDepth100(b *testing.B) { benchPath(b, tmpData(b, "deep", nestedJSON(100)), "deep") }
|
||||
func BenchmarkWideTokens100(b *testing.B) {
|
||||
benchPath(b, tmpData(b, "wide", wideTokenJSON(100)), "wide")
|
||||
}
|
||||
func BenchmarkWideTokens500(b *testing.B) {
|
||||
benchPath(b, tmpData(b, "wide", wideTokenJSON(500)), "wide")
|
||||
}
|
||||
Reference in New Issue
Block a user