Files
go-jdenticon/internal/renderer/micro_bench_test.go
Kevin McIntyre f1544ef49c
Some checks failed
CI / Test (Go 1.24.x, ubuntu-latest) (push) Successful in 1m53s
CI / Code Quality (push) Failing after 26s
CI / Security Scan (push) Failing after 11s
CI / Test Coverage (push) Successful in 1m13s
CI / Benchmarks (push) Failing after 10m22s
CI / Build CLI (push) Failing after 8s
Benchmarks / Run Benchmarks (push) Failing after 10m13s
Release / Test (push) Successful in 55s
Release / Build (amd64, darwin, ) (push) Failing after 12s
Release / Build (amd64, linux, ) (push) Failing after 6s
Release / Build (amd64, windows, .exe) (push) Failing after 12s
Release / Build (arm64, darwin, ) (push) Failing after 12s
Release / Build (arm64, linux, ) (push) Failing after 12s
Release / Release (push) Has been skipped
CI / Test (Go 1.24.x, macos-latest) (push) Has been cancelled
CI / Test (Go 1.24.x, windows-latest) (push) Has been cancelled
chore: update module path to gitea.dockr.co/kev/go-jdenticon
Move hosting from GitHub to private Gitea instance.
2026-02-10 10:07:57 -05:00

545 lines
14 KiB
Go

package renderer
import (
"strconv"
"strings"
"testing"
"gitea.dockr.co/kev/go-jdenticon/internal/engine"
)
// ============================================================================
// RENDERER MICRO-BENCHMARKS FOR MEMORY ALLOCATION ANALYSIS
// ============================================================================
var (
// Test data for renderer benchmarks
benchTestPoints = []engine.Point{
{X: 0.0, Y: 0.0},
{X: 10.5, Y: 0.0},
{X: 10.5, Y: 10.5},
{X: 0.0, Y: 10.5},
}
benchTestColors = []string{
"#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
}
benchTestSizes = []int{32, 64, 128, 256}
)
// ============================================================================
// SVG STRING BUILDING MICRO-BENCHMARKS
// ============================================================================
// BenchmarkSVGStringBuilding tests different string building patterns in SVG generation
func BenchmarkSVGStringBuilding(b *testing.B) {
points := benchTestPoints
b.Run("svgValue_formatting", func(b *testing.B) {
values := []float64{0.0, 10.5, 15.75, 100.0, 256.5}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
value := values[i%len(values)]
_ = svgValue(value)
}
})
b.Run("strconv_FormatFloat", func(b *testing.B) {
value := 10.5
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = strconv.FormatFloat(value, 'f', 1, 64)
}
})
b.Run("strconv_Itoa", func(b *testing.B) {
value := 10
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = strconv.Itoa(value)
}
})
b.Run("polygon_path_building", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var buf strings.Builder
buf.Grow(50) // Estimate capacity
// Simulate polygon path building
buf.WriteString("M")
buf.WriteString(svgValue(points[0].X))
buf.WriteString(" ")
buf.WriteString(svgValue(points[0].Y))
for j := 1; j < len(points); j++ {
buf.WriteString("L")
buf.WriteString(svgValue(points[j].X))
buf.WriteString(" ")
buf.WriteString(svgValue(points[j].Y))
}
buf.WriteString("Z")
_ = buf.String()
}
})
}
// BenchmarkSVGPathOperations tests SVGPath struct operations
func BenchmarkSVGPathOperations(b *testing.B) {
points := benchTestPoints
b.Run("SVGPath_AddPolygon", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
path := &SVGPath{}
path.AddPolygon(points)
}
})
b.Run("SVGPath_AddCircle", func(b *testing.B) {
topLeft := engine.Point{X: 5.0, Y: 5.0}
size := 10.0
b.ReportAllocs()
for i := 0; i < b.N; i++ {
path := &SVGPath{}
path.AddCircle(topLeft, size, false)
}
})
b.Run("SVGPath_DataString", func(b *testing.B) {
path := &SVGPath{}
path.AddPolygon(points)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = path.DataString()
}
})
}
// BenchmarkStringBuilderPooling tests the efficiency of string builder pooling
func BenchmarkStringBuilderPooling(b *testing.B) {
points := benchTestPoints
b.Run("direct_builder", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
// Use direct string builder (pool eliminated for direct writing)
var buf strings.Builder
// Build polygon path
buf.WriteString("M")
buf.WriteString(svgValue(points[0].X))
buf.WriteString(" ")
buf.WriteString(svgValue(points[0].Y))
for j := 1; j < len(points); j++ {
buf.WriteString("L")
buf.WriteString(svgValue(points[j].X))
buf.WriteString(" ")
buf.WriteString(svgValue(points[j].Y))
}
buf.WriteString("Z")
_ = buf.String()
}
})
b.Run("without_pool", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
// Create new buffer each time
var buf strings.Builder
buf.Grow(50)
// Build polygon path
buf.WriteString("M")
buf.WriteString(svgValue(points[0].X))
buf.WriteString(" ")
buf.WriteString(svgValue(points[0].Y))
for j := 1; j < len(points); j++ {
buf.WriteString("L")
buf.WriteString(svgValue(points[j].X))
buf.WriteString(" ")
buf.WriteString(svgValue(points[j].Y))
}
buf.WriteString("Z")
_ = buf.String()
}
})
b.Run("reused_builder", func(b *testing.B) {
var buf strings.Builder
buf.Grow(100) // Pre-allocate larger buffer
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
buf.Reset()
// Build polygon path
buf.WriteString("M")
buf.WriteString(svgValue(points[0].X))
buf.WriteString(" ")
buf.WriteString(svgValue(points[0].Y))
for j := 1; j < len(points); j++ {
buf.WriteString("L")
buf.WriteString(svgValue(points[j].X))
buf.WriteString(" ")
buf.WriteString(svgValue(points[j].Y))
}
buf.WriteString("Z")
_ = buf.String()
}
})
}
// ============================================================================
// SVG RENDERER MICRO-BENCHMARKS
// ============================================================================
// BenchmarkSVGRendererOperations tests SVG renderer creation and operations
func BenchmarkSVGRendererOperations(b *testing.B) {
sizes := benchTestSizes
colors := benchTestColors
b.Run("NewSVGRenderer", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
size := sizes[i%len(sizes)]
_ = NewSVGRenderer(size)
}
})
b.Run("BeginShape", func(b *testing.B) {
renderer := NewSVGRenderer(64)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
color := colors[i%len(colors)]
renderer.BeginShape(color)
}
})
b.Run("AddPolygon", func(b *testing.B) {
renderer := NewSVGRenderer(64)
renderer.BeginShape(colors[0])
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
renderer.AddPolygon(benchTestPoints)
}
})
b.Run("AddCircle", func(b *testing.B) {
renderer := NewSVGRenderer(64)
renderer.BeginShape(colors[0])
topLeft := engine.Point{X: 5.0, Y: 5.0}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
renderer.AddCircle(topLeft, 10.0, false)
}
})
}
// BenchmarkSVGGeneration tests full SVG generation with different scenarios
func BenchmarkSVGGeneration(b *testing.B) {
colors := benchTestColors[:3] // Use fewer colors for cleaner tests
b.Run("empty_renderer", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
renderer := NewSVGRenderer(64)
_ = renderer.ToSVG()
}
})
b.Run("single_shape", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
renderer := NewSVGRenderer(64)
renderer.BeginShape(colors[0])
renderer.AddPolygon(benchTestPoints)
renderer.EndShape()
_ = renderer.ToSVG()
}
})
b.Run("multiple_shapes", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
renderer := NewSVGRenderer(64)
for j, color := range colors {
renderer.BeginShape(color)
// Offset points for each shape
offsetPoints := make([]engine.Point, len(benchTestPoints))
for k, point := range benchTestPoints {
offsetPoints[k] = engine.Point{
X: point.X + float64(j*12),
Y: point.Y + float64(j*12),
}
}
renderer.AddPolygon(offsetPoints)
renderer.EndShape()
}
_ = renderer.ToSVG()
}
})
b.Run("with_background", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
renderer := NewSVGRenderer(64)
renderer.SetBackground("#ffffff", 1.0)
renderer.BeginShape(colors[0])
renderer.AddPolygon(benchTestPoints)
renderer.EndShape()
_ = renderer.ToSVG()
}
})
}
// BenchmarkSVGSizeEstimation tests SVG capacity estimation
func BenchmarkSVGSizeEstimation(b *testing.B) {
colors := benchTestColors
b.Run("capacity_estimation", func(b *testing.B) {
renderer := NewSVGRenderer(64)
for _, color := range colors {
renderer.BeginShape(color)
renderer.AddPolygon(benchTestPoints)
renderer.EndShape()
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
// Simulate capacity estimation logic from ToSVG
capacity := svgBaseOverheadBytes
capacity += svgBackgroundRectBytes // Assume background
// Estimate path data size
for _, color := range renderer.colorOrder {
path := renderer.pathsByColor[color]
if path != nil {
capacity += svgPathOverheadBytes + path.data.Len()
}
}
_ = capacity
}
})
b.Run("strings_builder_with_estimation", func(b *testing.B) {
renderer := NewSVGRenderer(64)
for _, color := range colors {
renderer.BeginShape(color)
renderer.AddPolygon(benchTestPoints)
renderer.EndShape()
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
// Test strings.Builder with capacity estimation
capacity := svgBaseOverheadBytes + svgBackgroundRectBytes
for _, color := range renderer.colorOrder {
path := renderer.pathsByColor[color]
if path != nil {
capacity += svgPathOverheadBytes + path.data.Len()
}
}
var svg strings.Builder
svg.Grow(capacity)
svg.WriteString(`<svg xmlns="http://www.w3.org/2000/svg">`)
svg.WriteString("</svg>")
_ = svg.String()
}
})
}
// ============================================================================
// MAP OPERATIONS MICRO-BENCHMARKS
// ============================================================================
// BenchmarkMapOperations tests map operations used in renderer
func BenchmarkMapOperations(b *testing.B) {
colors := benchTestColors
b.Run("map_creation", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
m := make(map[string]*SVGPath)
_ = m
}
})
b.Run("map_insertion", func(b *testing.B) {
m := make(map[string]*SVGPath)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
color := colors[i%len(colors)]
m[color] = &SVGPath{}
}
})
b.Run("map_lookup", func(b *testing.B) {
m := make(map[string]*SVGPath)
for _, color := range colors {
m[color] = &SVGPath{}
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
color := colors[i%len(colors)]
_ = m[color]
}
})
b.Run("map_existence_check", func(b *testing.B) {
m := make(map[string]*SVGPath)
for _, color := range colors {
m[color] = &SVGPath{}
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
color := colors[i%len(colors)]
_, exists := m[color]
_ = exists
}
})
}
// ============================================================================
// SLICE OPERATIONS MICRO-BENCHMARKS
// ============================================================================
// BenchmarkSliceOperations tests slice operations for color ordering
func BenchmarkSliceOperations(b *testing.B) {
colors := benchTestColors
b.Run("slice_append", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var colorOrder []string
//lint:ignore S1011 Intentionally benchmarking individual appends vs batch
//nolint:gosimple // Intentionally benchmarking individual appends vs batch
for _, color := range colors {
colorOrder = append(colorOrder, color)
}
_ = colorOrder
}
})
b.Run("slice_with_capacity", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
colorOrder := make([]string, 0, len(colors))
//lint:ignore S1011 Intentionally benchmarking individual appends with pre-allocation
//nolint:gosimple // Intentionally benchmarking individual appends with pre-allocation
for _, color := range colors {
colorOrder = append(colorOrder, color)
}
_ = colorOrder
}
})
b.Run("slice_iteration", func(b *testing.B) {
colorOrder := make([]string, len(colors))
copy(colorOrder, colors)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, color := range colorOrder {
_ = color
}
}
})
}
// ============================================================================
// COORDINATE TRANSFORMATION MICRO-BENCHMARKS
// ============================================================================
// BenchmarkCoordinateTransforms tests coordinate transformation patterns
func BenchmarkCoordinateTransforms(b *testing.B) {
points := benchTestPoints
b.Run("point_creation", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = engine.Point{X: 10.5, Y: 20.5}
}
})
b.Run("point_slice_creation", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
pointsCopy := make([]engine.Point, len(points))
copy(pointsCopy, points)
_ = pointsCopy
}
})
b.Run("point_transformation", func(b *testing.B) {
transform := func(x, y float64) (float64, float64) {
return x * 2.0, y * 2.0
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
transformedPoints := make([]engine.Point, len(points))
for j, point := range points {
newX, newY := transform(point.X, point.Y)
transformedPoints[j] = engine.Point{X: newX, Y: newY}
}
_ = transformedPoints
}
})
}
// ============================================================================
// MEMORY ALLOCATION PATTERN COMPARISONS
// ============================================================================
// BenchmarkAllocationPatterns compares different allocation patterns used in rendering
func BenchmarkAllocationPatterns(b *testing.B) {
b.Run("string_concatenation", func(b *testing.B) {
base := "test"
suffix := "value"
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = base + suffix
}
})
b.Run("sprintf_formatting", func(b *testing.B) {
value := 10.5
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = strconv.FormatFloat(value, 'f', 1, 64)
}
})
b.Run("builder_small_capacity", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var buf strings.Builder
buf.Grow(10)
buf.WriteString("test")
buf.WriteString("value")
_ = buf.String()
}
})
b.Run("builder_large_capacity", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var buf strings.Builder
buf.Grow(100)
buf.WriteString("test")
buf.WriteString("value")
_ = buf.String()
}
})
}