2026-09-17 19:54:27 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
|
|
package interpres
|
|
|
|
|
|
|
|
|
|
import (
|
2026-09-22 15:59:03 +02:00
|
|
|
"context"
|
2026-09-17 19:54:27 +02:00
|
|
|
"fmt"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// benchDoc is a representative configuration document: every scalar kind, an
|
|
|
|
|
// inline table, sub-tables, and an array of tables.
|
|
|
|
|
var benchDoc = []byte(`title = "benchmark configuration"
|
|
|
|
|
replicas = 3
|
|
|
|
|
ratio = 0.75
|
|
|
|
|
enabled = true
|
|
|
|
|
when = 2026-09-17T12:00:00Z
|
|
|
|
|
local = 2026-09-17T12:00:00
|
|
|
|
|
tags = ["alpha", "beta", "gamma"]
|
|
|
|
|
limits = { cpu = 4, memory = 1024 }
|
|
|
|
|
|
|
|
|
|
[server]
|
|
|
|
|
host = "localhost"
|
|
|
|
|
port = 8080
|
|
|
|
|
hosts = ["a.example", "b.example"]
|
|
|
|
|
|
|
|
|
|
[server.tls]
|
|
|
|
|
enabled = true
|
|
|
|
|
cert = "/etc/cert.pem"
|
|
|
|
|
|
|
|
|
|
[[items]]
|
|
|
|
|
name = "first"
|
|
|
|
|
weight = 10
|
|
|
|
|
flags = ["x", "y"]
|
|
|
|
|
|
|
|
|
|
[[items]]
|
|
|
|
|
name = "second"
|
|
|
|
|
weight = 20
|
|
|
|
|
flags = ["z"]
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
// longDoc is generated once so the large-input benchmarks measure parsing,
|
|
|
|
|
// not document construction. Roughly 2000 array-of-tables entries.
|
|
|
|
|
var longDoc = func() []byte {
|
|
|
|
|
var b strings.Builder
|
|
|
|
|
b.WriteString("title = \"long\"\n")
|
|
|
|
|
for i := range 2000 {
|
|
|
|
|
fmt.Fprintf(&b, "[[entry]]\nname = \"entry-%d\"\nweight = %d\nwhen = 2026-09-17T12:00:00Z\nratio = 0.5\ntags = [\"a\", \"b\", \"c\"]\n\n", i, i)
|
|
|
|
|
}
|
|
|
|
|
return []byte(b.String())
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
type benchTLS struct {
|
|
|
|
|
Enabled bool `toml:"enabled"`
|
|
|
|
|
Cert string `toml:"cert"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type benchServer struct {
|
|
|
|
|
Host string `toml:"host"`
|
|
|
|
|
Port int `toml:"port"`
|
|
|
|
|
Hosts []string `toml:"hosts"`
|
|
|
|
|
TLS benchTLS `toml:"tls"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type benchItem struct {
|
|
|
|
|
Name string `toml:"name"`
|
|
|
|
|
Weight int `toml:"weight"`
|
|
|
|
|
Flags []string `toml:"flags"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type benchConfig struct {
|
|
|
|
|
Title string `toml:"title"`
|
|
|
|
|
Replicas int `toml:"replicas"`
|
|
|
|
|
Ratio float64 `toml:"ratio"`
|
|
|
|
|
Enabled bool `toml:"enabled"`
|
|
|
|
|
When time.Time `toml:"when"`
|
|
|
|
|
Local LocalDateTime `toml:"local"`
|
|
|
|
|
Tags []string `toml:"tags"`
|
|
|
|
|
Limits map[string]any `toml:"limits"`
|
|
|
|
|
Server benchServer `toml:"server"`
|
|
|
|
|
Items []benchItem `toml:"items"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkParse(b *testing.B) {
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
b.SetBytes(int64(len(benchDoc)))
|
|
|
|
|
for b.Loop() {
|
2026-09-20 10:40:57 +02:00
|
|
|
if _, err := ParseMap(benchDoc); err != nil {
|
2026-09-17 19:54:27 +02:00
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkMarshal(b *testing.B) {
|
2026-09-20 10:40:57 +02:00
|
|
|
tree, err := ParseMap(benchDoc)
|
2026-09-17 19:54:27 +02:00
|
|
|
if err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
b.SetBytes(int64(len(benchDoc)))
|
|
|
|
|
for b.Loop() {
|
|
|
|
|
if _, err := Marshal(tree); err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkStrictDecode(b *testing.B) {
|
|
|
|
|
dec := NewDecoder().DisallowUnknownFields()
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
for b.Loop() {
|
|
|
|
|
var cfg benchConfig
|
|
|
|
|
if err := dec.Decode(benchDoc, &cfg); err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkParseLong(b *testing.B) {
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
b.SetBytes(int64(len(longDoc)))
|
|
|
|
|
for b.Loop() {
|
2026-09-20 10:40:57 +02:00
|
|
|
if _, err := ParseMap(longDoc); err != nil {
|
2026-09-17 19:54:27 +02:00
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-20 22:15:10 +02:00
|
|
|
|
|
|
|
|
// benchLongEntry mirrors one [[entry]] element of longDoc for the typed
|
|
|
|
|
// decode of the long document.
|
|
|
|
|
type benchLongEntry struct {
|
|
|
|
|
Name string `toml:"name"`
|
|
|
|
|
Weight int `toml:"weight"`
|
|
|
|
|
When time.Time `toml:"when"`
|
|
|
|
|
Ratio float64 `toml:"ratio"`
|
|
|
|
|
Tags []string `toml:"tags"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type benchLongDoc struct {
|
|
|
|
|
Title string `toml:"title"`
|
|
|
|
|
Entry []benchLongEntry `toml:"entry"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkStrictDecodeLong(b *testing.B) {
|
|
|
|
|
dec := NewDecoder().DisallowUnknownFields()
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
b.SetBytes(int64(len(longDoc)))
|
|
|
|
|
for b.Loop() {
|
|
|
|
|
var doc benchLongDoc
|
|
|
|
|
if err := dec.Decode(longDoc, &doc); err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkMarshalLong(b *testing.B) {
|
|
|
|
|
tree, err := ParseMap(longDoc)
|
|
|
|
|
if err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
b.SetBytes(int64(len(longDoc)))
|
|
|
|
|
for b.Loop() {
|
|
|
|
|
if _, err := Marshal(tree); err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-22 15:59:03 +02:00
|
|
|
|
|
|
|
|
// BenchmarkStrictDecodeTree measures the reference path the targeted decode
|
|
|
|
|
// is measured against: the full tree parse followed by the reflection walk.
|
|
|
|
|
// The pair runs in one process, so the A/B comparison shares the machine.
|
|
|
|
|
func BenchmarkStrictDecodeTree(b *testing.B) {
|
|
|
|
|
dec := newDecoder()
|
|
|
|
|
dec.disallowUnknown = true
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
for b.Loop() {
|
|
|
|
|
tree, _, err := parseWithOptions(context.Background(), benchDoc, parseOptions{}, false)
|
|
|
|
|
if err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
var cfg benchConfig
|
|
|
|
|
if err := dec.decode(tree, &cfg); err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkStrictDecodeTreeLong(b *testing.B) {
|
|
|
|
|
dec := newDecoder()
|
|
|
|
|
dec.disallowUnknown = true
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
b.SetBytes(int64(len(longDoc)))
|
|
|
|
|
for b.Loop() {
|
|
|
|
|
tree, _, err := parseWithOptions(context.Background(), longDoc, parseOptions{}, false)
|
|
|
|
|
if err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
var doc benchLongDoc
|
|
|
|
|
if err := dec.decode(tree, &doc); err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|