@@ -3,10 +3,13 @@ package typeid_test
33
44import (
55 "fmt"
6+ "math/rand"
7+ "runtime"
68 "testing"
79
810 "github.com/gofrs/uuid/v5"
911 "go.jetify.com/typeid"
12+ "go.jetify.com/typeid/base32"
1013)
1114
1215func BenchmarkNew (b * testing.B ) {
@@ -28,6 +31,25 @@ func BenchmarkNew(b *testing.B) {
2831 uuid .NewV7 ()
2932 }
3033 })
34+ // Add benchmark for different prefix lengths
35+ b .Run ("prefix=short" , func (b * testing.B ) {
36+ b .ReportAllocs ()
37+ for i := 0 ; i < b .N ; i ++ {
38+ typeid .WithPrefix ("s" )
39+ }
40+ })
41+ b .Run ("prefix=medium" , func (b * testing.B ) {
42+ b .ReportAllocs ()
43+ for i := 0 ; i < b .N ; i ++ {
44+ typeid .WithPrefix ("medium" )
45+ }
46+ })
47+ b .Run ("prefix=long" , func (b * testing.B ) {
48+ b .ReportAllocs ()
49+ for i := 0 ; i < b .N ; i ++ {
50+ typeid .WithPrefix ("thisislongprefix" )
51+ }
52+ })
3153}
3254
3355func BenchmarkString (b * testing.B ) {
@@ -349,6 +371,226 @@ func BenchmarkEncodeDecode(b *testing.B) {
349371 }
350372}
351373
374+ // Benchmark Base32 operations directly
375+ func BenchmarkBase32 (b * testing.B ) {
376+ b .Run ("encode" , func (b * testing.B ) {
377+ uid := uuid .Must (uuid .NewV7 ())
378+ var bytes [16 ]byte
379+ copy (bytes [:], uid .Bytes ())
380+ b .ReportAllocs ()
381+ b .ResetTimer ()
382+ for i := 0 ; i < b .N ; i ++ {
383+ _ = base32 .Encode (bytes )
384+ }
385+ })
386+ b .Run ("decode" , func (b * testing.B ) {
387+ uid := uuid .Must (uuid .NewV7 ())
388+ var bytes [16 ]byte
389+ copy (bytes [:], uid .Bytes ())
390+ encoded := base32 .Encode (bytes )
391+ b .ReportAllocs ()
392+ b .ResetTimer ()
393+ for i := 0 ; i < b .N ; i ++ {
394+ _ , _ = base32 .Decode (encoded )
395+ }
396+ })
397+ }
398+
399+ // Benchmark memory usage with different batch sizes
400+ func BenchmarkMemoryUsage (b * testing.B ) {
401+ benchSizes := []int {100 , 1000 , 10000 }
402+ for _ , size := range benchSizes {
403+ size := size // capture range variable
404+ b .Run (fmt .Sprintf ("size=%d" , size ), func (b * testing.B ) {
405+ // Pre-allocate a slice to avoid measuring slice growth
406+ ids := make ([]typeid.AnyID , size )
407+ b .Cleanup (func () {
408+ // Clear the slice to help GC
409+ for i := range ids {
410+ ids [i ] = typeid.AnyID {}
411+ }
412+ ids = nil
413+ })
414+
415+ b .ReportAllocs ()
416+ b .SetBytes (int64 (size * 16 )) // Each UUID is 16 bytes
417+ b .ResetTimer ()
418+
419+ for i := 0 ; i < b .N ; i ++ {
420+ for j := range ids {
421+ ids [j ] = typeid .Must (typeid .WithPrefix ("prefix" ))
422+ }
423+ // Force memory pressure to ensure GC behavior is measured
424+ runtime .GC ()
425+ }
426+ })
427+ }
428+ }
429+
430+ // Benchmark validation
431+ func BenchmarkValidation (b * testing.B ) {
432+ validIDs := make ([]string , 100 )
433+ invalidIDs := make ([]string , 100 )
434+
435+ for i := range validIDs {
436+ validIDs [i ] = typeid .Must (typeid .WithPrefix ("prefix" )).String ()
437+ if i < len (invalidIDs ) {
438+ // Create definitely invalid IDs by:
439+ // 1. Using invalid prefix characters
440+ // 2. Wrong separator
441+ // 3. Invalid base32 characters
442+ switch i % 3 {
443+ case 0 :
444+ // Invalid prefix (contains number)
445+ invalidIDs [i ] = "prefix1_01h2xcejqtf2nbrexx3vqjhp41"
446+ case 1 :
447+ // Wrong separator (using . instead of _)
448+ invalidIDs [i ] = "prefix.01h2xcejqtf2nbrexx3vqjhp41"
449+ case 2 :
450+ // Invalid base32 character in suffix (using 'u' which isn't in the alphabet)
451+ invalidIDs [i ] = "prefix_u1h2xcejqtf2nbrexx3vqjhp41"
452+ }
453+ }
454+ }
455+
456+ b .Run ("valid" , func (b * testing.B ) {
457+ b .ReportAllocs ()
458+ b .ResetTimer ()
459+
460+ for i := 0 ; i < b .N ; i ++ {
461+ // Test all valid IDs in each iteration
462+ for _ , id := range validIDs {
463+ _ , err := typeid .FromString (id )
464+ if err != nil {
465+ b .Fatalf ("Expected valid ID to pass validation: %v" , err )
466+ }
467+ }
468+ }
469+ })
470+
471+ b .Run ("invalid" , func (b * testing.B ) {
472+ b .ReportAllocs ()
473+ b .ResetTimer ()
474+
475+ for i := 0 ; i < b .N ; i ++ {
476+ // Test all invalid IDs in each iteration
477+ for _ , id := range invalidIDs {
478+ _ , err := typeid .FromString (id )
479+ if err == nil {
480+ b .Fatalf ("Expected invalid ID to fail validation for ID: %s" , id )
481+ }
482+ }
483+ }
484+ })
485+ }
486+
487+ // Benchmark parallel ID generation
488+ func BenchmarkParallelGeneration (b * testing.B ) {
489+ benchCases := []struct {
490+ name string
491+ procs int
492+ batchSize int
493+ }{
494+ {"procs=4_batch=100" , 4 , 100 },
495+ {"procs=8_batch=100" , runtime .GOMAXPROCS (0 ) * 2 , 100 },
496+ {"procs=4_batch=1000" , 4 , 1000 },
497+ {"procs=8_batch=1000" , runtime .GOMAXPROCS (0 ) * 2 , 1000 },
498+ }
499+
500+ for _ , bc := range benchCases {
501+ bc := bc // capture range variable
502+ b .Run (bc .name , func (b * testing.B ) {
503+ // Pre-allocate a slice of prefixes for each processor
504+ prefixes := make ([]string , bc .procs )
505+ for i := range prefixes {
506+ // Use valid prefixes with only [a-z_]
507+ prefixes [i ] = fmt .Sprintf ("prefix_%c" , 'a' + i )
508+ }
509+
510+ b .SetParallelism (bc .procs )
511+ b .ReportAllocs ()
512+ b .ResetTimer ()
513+
514+ b .RunParallel (func (pb * testing.PB ) {
515+ // Pre-allocate slice with capacity
516+ ids := make ([]typeid.AnyID , 0 , bc .batchSize )
517+ // Keep track of which prefix we're using
518+ prefixIdx := 0
519+
520+ for pb .Next () {
521+ // Clear the slice but keep capacity
522+ ids = ids [:0 ]
523+
524+ // Use all prefixes in each iteration
525+ for j := 0 ; j < bc .batchSize ; j ++ {
526+ // Cycle through prefixes
527+ prefix := prefixes [prefixIdx % len (prefixes )]
528+ prefixIdx ++
529+ ids = append (ids , typeid .Must (typeid .WithPrefix (prefix )))
530+ }
531+ }
532+ })
533+ })
534+ }
535+ }
536+
537+ // Benchmark mixed operations to simulate real-world usage patterns
538+ func BenchmarkMixedOperations (b * testing.B ) {
539+ // Pre-generate all operations and test data
540+ type op struct {
541+ kind string
542+ id typeid.AnyID // For operations that need an existing ID
543+ }
544+
545+ // Create a slice of operations in the ratio we want to test
546+ numOps := 100 // Number of operations to pre-generate
547+ ops := make ([]op , numOps )
548+ ids := make ([]typeid.AnyID , numOps / 2 ) // Pre-generate some IDs for operations that need them
549+
550+ for i := range ids {
551+ ids [i ] = typeid .Must (typeid .WithPrefix ("prefix" ))
552+ }
553+
554+ // Initialize random number generator
555+ src := rand .NewSource (1234 )
556+ rnd := rand .New (src )
557+
558+ // Generate operations in desired ratios
559+ for i := range ops {
560+ r := rnd .Float32 ()
561+ switch {
562+ case r < 0.1 : // 10% new IDs
563+ ops [i ] = op {kind : "create" }
564+ case r < 0.4 : // 30% toString
565+ ops [i ] = op {kind : "toString" , id : ids [rnd .Intn (len (ids ))]}
566+ case r < 0.7 : // 30% parse
567+ ops [i ] = op {kind : "parse" , id : ids [rnd .Intn (len (ids ))]}
568+ default : // 30% validate
569+ ops [i ] = op {kind : "validate" , id : ids [rnd .Intn (len (ids ))]}
570+ }
571+ }
572+
573+ b .ReportAllocs ()
574+ b .ResetTimer ()
575+
576+ // Ensure we go through all operations at least once
577+ for i := 0 ; i < b .N ; i ++ {
578+ // Do all operations once per iteration
579+ for _ , op := range ops {
580+ switch op .kind {
581+ case "create" :
582+ _ = typeid .Must (typeid .WithPrefix ("prefix" ))
583+ case "toString" :
584+ _ = op .id .String ()
585+ case "parse" :
586+ _ , _ = typeid .FromString (op .id .String ())
587+ case "validate" :
588+ _ , _ = typeid .FromString (op .id .String ())
589+ }
590+ }
591+ }
592+ }
593+
352594// TODO: define these in a shared file if we're gonna use in several tests.
353595
354596type TestPrefix struct {}
0 commit comments