Initial release: Go Jdenticon library v0.1.0
- Core library with SVG and PNG generation - CLI tool with generate and batch commands - Cross-platform path handling for Windows compatibility - Comprehensive test suite with integration tests
This commit is contained in:
413
internal/engine/generator_bench_test.go
Normal file
413
internal/engine/generator_bench_test.go
Normal file
@@ -0,0 +1,413 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ungluedlabs/go-jdenticon/internal/util"
|
||||
)
|
||||
|
||||
var benchmarkHashes = []string{
|
||||
"7c4a8d09ca3762af61e59520943dc26494f8941b", // test-hash
|
||||
"b36d9b6a07d0b5bfb7e0e77a7f8d1e5e6f7a8b9c", // example1@gmail.com
|
||||
"a9d8e7f6c5b4a3d2e1f0e9d8c7b6a5d4e3f2a1b0", // example2@yahoo.com
|
||||
"1234567890abcdef1234567890abcdef12345678",
|
||||
"fedcba0987654321fedcba0987654321fedcba09",
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"0000000000000000000000000000000000000000",
|
||||
"ffffffffffffffffffffffffffffffffffffffffffff",
|
||||
}
|
||||
|
||||
var benchmarkSizesFloat = []float64{
|
||||
16.0, 32.0, 64.0, 128.0, 256.0, 512.0,
|
||||
}
|
||||
|
||||
// Benchmark core generator creation
|
||||
func BenchmarkNewGeneratorWithConfig(b *testing.B) {
|
||||
config := GeneratorConfig{
|
||||
ColorConfig: DefaultColorConfig(),
|
||||
CacheSize: 1000,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
generator, err := NewGeneratorWithConfig(config)
|
||||
if err != nil {
|
||||
b.Fatalf("NewGeneratorWithConfig failed: %v", err)
|
||||
}
|
||||
_ = generator
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark icon generation without cache (per size)
|
||||
func BenchmarkGenerateWithoutCachePerSize(b *testing.B) {
|
||||
config := GeneratorConfig{
|
||||
ColorConfig: DefaultColorConfig(),
|
||||
CacheSize: 1000,
|
||||
}
|
||||
generator, err := NewGeneratorWithConfig(config)
|
||||
if err != nil {
|
||||
b.Fatalf("NewGeneratorWithConfig failed: %v", err)
|
||||
}
|
||||
|
||||
for _, size := range benchmarkSizesFloat {
|
||||
b.Run(fmt.Sprintf("size-%.0f", size), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
hash := benchmarkHashes[i%len(benchmarkHashes)]
|
||||
_, err := generator.GenerateWithoutCache(context.Background(), hash, size)
|
||||
if err != nil {
|
||||
b.Fatalf("GenerateWithoutCache failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark icon generation with cache (different from generator_test.go)
|
||||
func BenchmarkGenerateWithCacheHeavy(b *testing.B) {
|
||||
config := GeneratorConfig{
|
||||
ColorConfig: DefaultColorConfig(),
|
||||
CacheSize: 100,
|
||||
}
|
||||
generator, err := NewGeneratorWithConfig(config)
|
||||
if err != nil {
|
||||
b.Fatalf("NewGeneratorWithConfig failed: %v", err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Use limited set of hashes to test cache hits
|
||||
hash := benchmarkHashes[i%3] // Only use first 3 hashes
|
||||
size := 64.0
|
||||
_, err := generator.Generate(context.Background(), hash, size)
|
||||
if err != nil {
|
||||
b.Fatalf("Generate failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark hash parsing functions
|
||||
func BenchmarkParseHex(b *testing.B) {
|
||||
hash := "7c4a8d09ca3762af61e59520943dc26494f8941b"
|
||||
|
||||
b.Run("offset2_len1", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = util.ParseHex(hash, 2, 1)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("offset4_len1", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = util.ParseHex(hash, 4, 1)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("offset1_len1", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = util.ParseHex(hash, 1, 1)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("offset8_len3", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = util.ParseHex(hash, 8, 3)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Benchmark hue extraction
|
||||
func BenchmarkExtractHue(b *testing.B) {
|
||||
config := GeneratorConfig{
|
||||
ColorConfig: DefaultColorConfig(),
|
||||
CacheSize: 1,
|
||||
}
|
||||
generator, err := NewGeneratorWithConfig(config)
|
||||
if err != nil {
|
||||
b.Fatalf("NewGeneratorWithConfig failed: %v", err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
hash := benchmarkHashes[i%len(benchmarkHashes)]
|
||||
_, _ = generator.extractHue(hash)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark shape selection
|
||||
func BenchmarkShapeSelection(b *testing.B) {
|
||||
hash := "7c4a8d09ca3762af61e59520943dc26494f8941b"
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Simulate shape selection process using util.ParseHex
|
||||
sideShapeIndex, _ := util.ParseHex(hash, hashPosSideShape, 1)
|
||||
cornerShapeIndex, _ := util.ParseHex(hash, hashPosCornerShape, 1)
|
||||
centerShapeIndex, _ := util.ParseHex(hash, hashPosCenterShape, 1)
|
||||
|
||||
// Use modulo with arbitrary shape counts (simulating actual shape arrays)
|
||||
sideShapeIndex = sideShapeIndex % 16 // Assume 16 outer shapes
|
||||
cornerShapeIndex = cornerShapeIndex % 16
|
||||
centerShapeIndex = centerShapeIndex % 8 // Assume 8 center shapes
|
||||
|
||||
_, _, _ = sideShapeIndex, cornerShapeIndex, centerShapeIndex
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark color theme generation
|
||||
func BenchmarkGenerateColorTheme(b *testing.B) {
|
||||
config := DefaultColorConfig()
|
||||
generator, err := NewGeneratorWithConfig(GeneratorConfig{
|
||||
ColorConfig: config,
|
||||
CacheSize: 1,
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatalf("NewGeneratorWithConfig failed: %v", err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
hash := benchmarkHashes[i%len(benchmarkHashes)]
|
||||
hue, _ := generator.extractHue(hash)
|
||||
_ = GenerateColorTheme(hue, config)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark position computation
|
||||
func BenchmarkComputePositions(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Test both side and corner positions
|
||||
_ = getSidePositions()
|
||||
_ = getCornerPositions()
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark transform applications
|
||||
func BenchmarkTransformApplication(b *testing.B) {
|
||||
transform := Transform{
|
||||
x: 1.0,
|
||||
y: 2.0,
|
||||
size: 64.0,
|
||||
rotation: 1,
|
||||
}
|
||||
|
||||
b.Run("center_point", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = transform.TransformIconPoint(0.5, 0.5, 0, 0)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("corner_point", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = transform.TransformIconPoint(1.0, 1.0, 0, 0)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("origin_point", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = transform.TransformIconPoint(0.0, 0.0, 0, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Benchmark icon size calculations
|
||||
func BenchmarkIconSizeCalculations(b *testing.B) {
|
||||
sizes := benchmarkSizesFloat
|
||||
padding := 0.1
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
size := sizes[i%len(sizes)]
|
||||
// Simulate size calculations from generator
|
||||
paddingPixels := size * padding * paddingMultiple
|
||||
iconSize := size - paddingPixels
|
||||
cellSize := iconSize / gridSize
|
||||
|
||||
_, _, _ = paddingPixels, iconSize, cellSize
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark cache key generation
|
||||
func BenchmarkCacheKeyGeneration(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
hash := benchmarkHashes[i%len(benchmarkHashes)]
|
||||
size := benchmarkSizesFloat[i%len(benchmarkSizesFloat)]
|
||||
_ = benchmarkCacheKey(hash, size)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to simulate cache key generation
|
||||
func benchmarkCacheKey(hash string, size float64) string {
|
||||
return hash + ":" + fmt.Sprintf("%.0f", size)
|
||||
}
|
||||
|
||||
// Benchmark full icon generation pipeline
|
||||
func BenchmarkFullGenerationPipeline(b *testing.B) {
|
||||
config := GeneratorConfig{
|
||||
ColorConfig: DefaultColorConfig(),
|
||||
CacheSize: 1, // Minimal cache to avoid cache hits
|
||||
}
|
||||
generator, err := NewGeneratorWithConfig(config)
|
||||
if err != nil {
|
||||
b.Fatalf("NewGeneratorWithConfig failed: %v", err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
hash := benchmarkHashes[i%len(benchmarkHashes)]
|
||||
size := 64.0
|
||||
|
||||
// This tests the full pipeline: hash parsing, color generation,
|
||||
// shape selection, positioning, and rendering preparation
|
||||
_, err := generator.GenerateWithoutCache(context.Background(), hash, size)
|
||||
if err != nil {
|
||||
b.Fatalf("GenerateWithoutCache failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark different grid sizes (theoretical)
|
||||
func BenchmarkGridSizeCalculations(b *testing.B) {
|
||||
sizes := benchmarkSizesFloat
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
size := sizes[i%len(sizes)]
|
||||
padding := 0.1
|
||||
|
||||
// Test calculations for different theoretical grid sizes
|
||||
for gridSizeTest := 3; gridSizeTest <= 6; gridSizeTest++ {
|
||||
paddingPixels := size * padding * paddingMultiple
|
||||
iconSize := size - paddingPixels
|
||||
cellSize := iconSize / float64(gridSizeTest)
|
||||
_ = cellSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark color conflict resolution
|
||||
func BenchmarkColorConflictResolution(b *testing.B) {
|
||||
config := DefaultColorConfig()
|
||||
generator, err := NewGeneratorWithConfig(GeneratorConfig{
|
||||
ColorConfig: config,
|
||||
CacheSize: 1,
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatalf("NewGeneratorWithConfig failed: %v", err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
hash := benchmarkHashes[i%len(benchmarkHashes)]
|
||||
hue, _ := generator.extractHue(hash)
|
||||
colorTheme := GenerateColorTheme(hue, config)
|
||||
|
||||
// Simulate color conflict resolution
|
||||
for j := 0; j < 5; j++ {
|
||||
colorHash, _ := util.ParseHex(hash, hashPosColorStart+j%3, 1)
|
||||
selectedColor := colorTheme[colorHash%len(colorTheme)]
|
||||
_ = selectedColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get side positions (matching generator logic)
|
||||
func getSidePositions() [][]int {
|
||||
return [][]int{{1, 0}, {2, 0}, {2, 3}, {1, 3}, {0, 1}, {3, 1}, {3, 2}, {0, 2}}
|
||||
}
|
||||
|
||||
// Helper function to get corner positions (matching generator logic)
|
||||
func getCornerPositions() [][]int {
|
||||
return [][]int{{0, 0}, {3, 0}, {3, 3}, {0, 3}}
|
||||
}
|
||||
|
||||
// Benchmark concurrent icon generation for high-traffic scenarios
|
||||
func BenchmarkGenerateWithoutCacheParallel(b *testing.B) {
|
||||
config := GeneratorConfig{
|
||||
ColorConfig: DefaultColorConfig(),
|
||||
CacheSize: 1, // Minimal cache to avoid cache effects
|
||||
}
|
||||
generator, err := NewGeneratorWithConfig(config)
|
||||
if err != nil {
|
||||
b.Fatalf("NewGeneratorWithConfig failed: %v", err)
|
||||
}
|
||||
|
||||
for _, size := range []float64{64.0, 128.0, 256.0} {
|
||||
b.Run(fmt.Sprintf("size-%.0f", size), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
hash := benchmarkHashes[i%len(benchmarkHashes)]
|
||||
_, err := generator.GenerateWithoutCache(context.Background(), hash, size)
|
||||
if err != nil {
|
||||
b.Errorf("GenerateWithoutCache failed: %v", err)
|
||||
}
|
||||
i++
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark concurrent cached generation
|
||||
func BenchmarkGenerateWithCacheParallel(b *testing.B) {
|
||||
config := GeneratorConfig{
|
||||
ColorConfig: DefaultColorConfig(),
|
||||
CacheSize: 100, // Shared cache for concurrent access
|
||||
}
|
||||
generator, err := NewGeneratorWithConfig(config)
|
||||
if err != nil {
|
||||
b.Fatalf("NewGeneratorWithConfig failed: %v", err)
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
// Use limited set of hashes to test cache hits under concurrency
|
||||
hash := benchmarkHashes[i%3] // Only use first 3 hashes
|
||||
size := 64.0
|
||||
_, err := generator.Generate(context.Background(), hash, size)
|
||||
if err != nil {
|
||||
b.Errorf("Generate failed: %v", err)
|
||||
}
|
||||
i++
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user