// Pool and PubSub Benchmark Suite // // This file contains comprehensive benchmarks for both pool operations and PubSub initialization. // It's designed to be run against different branches to compare performance. // // Usage Examples: // # Run all benchmarks // go test -bench=. -run='^$' -benchtime=1s pool_pubsub_bench_test.go // // # Run only pool benchmarks // go test -bench=BenchmarkPool -run='^$' pool_pubsub_bench_test.go // // # Run only PubSub benchmarks // go test -bench=BenchmarkPubSub -run='^$' pool_pubsub_bench_test.go // // # Compare between branches // git checkout branch1 && go test -bench=. -run='^$' pool_pubsub_bench_test.go > branch1.txt // git checkout branch2 && go test -bench=. -run='^$' pool_pubsub_bench_test.go > branch2.txt // benchcmp branch1.txt branch2.txt // // # Run with memory profiling // go test -bench=BenchmarkPoolGetPut -run='^$' -memprofile=mem.prof pool_pubsub_bench_test.go // // # Run with CPU profiling // go test -bench=BenchmarkPoolGetPut -run='^$' -cpuprofile=cpu.prof pool_pubsub_bench_test.go package redis_test import ( "context" "fmt" "net" "sync" "testing" "time" "github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9/internal/pool" ) // dummyDialer creates a mock connection for benchmarking func dummyDialer(ctx context.Context) (net.Conn, error) { return &dummyConn{}, nil } // dummyConn implements net.Conn for benchmarking type dummyConn struct{} func (c *dummyConn) Read(b []byte) (n int, err error) { return len(b), nil } func (c *dummyConn) Write(b []byte) (n int, err error) { return len(b), nil } func (c *dummyConn) Close() error { return nil } func (c *dummyConn) LocalAddr() net.Addr { return &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 6379} } func (c *dummyConn) RemoteAddr() net.Addr { return &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 6379} } func (c *dummyConn) SetDeadline(t time.Time) error { return nil } func (c *dummyConn) SetReadDeadline(t time.Time) error { return nil } func (c *dummyConn) SetWriteDeadline(t time.Time) error { return nil } // ============================================================================= // POOL BENCHMARKS // ============================================================================= // BenchmarkPoolGetPut benchmarks the core pool Get/Put operations func BenchmarkPoolGetPut(b *testing.B) { ctx := context.Background() poolSizes := []int{1, 2, 4, 8, 16, 32, 64, 128} for _, poolSize := range poolSizes { b.Run(fmt.Sprintf("PoolSize_%d", poolSize), func(b *testing.B) { connPool := pool.NewConnPool(&pool.Options{ Dialer: dummyDialer, PoolSize: int32(poolSize), MaxConcurrentDials: poolSize, PoolTimeout: time.Second, DialTimeout: time.Second, ConnMaxIdleTime: time.Hour, MinIdleConns: int32(0), // Start with no idle connections }) defer connPool.Close() b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { cn, err := connPool.Get(ctx) if err != nil { b.Fatal(err) } connPool.Put(ctx, cn) } }) }) } } // BenchmarkPoolGetPutWithMinIdle benchmarks pool operations with MinIdleConns func BenchmarkPoolGetPutWithMinIdle(b *testing.B) { ctx := context.Background() configs := []struct { poolSize int minIdleConns int }{ {8, 2}, {16, 4}, {32, 8}, {64, 16}, } for _, config := range configs { b.Run(fmt.Sprintf("Pool_%d_MinIdle_%d", config.poolSize, config.minIdleConns), func(b *testing.B) { connPool := pool.NewConnPool(&pool.Options{ Dialer: dummyDialer, PoolSize: int32(config.poolSize), MaxConcurrentDials: config.poolSize, MinIdleConns: int32(config.minIdleConns), PoolTimeout: time.Second, DialTimeout: time.Second, ConnMaxIdleTime: time.Hour, }) defer connPool.Close() b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { cn, err := connPool.Get(ctx) if err != nil { b.Fatal(err) } connPool.Put(ctx, cn) } }) }) } } // BenchmarkPoolConcurrentGetPut benchmarks pool under high concurrency func BenchmarkPoolConcurrentGetPut(b *testing.B) { ctx := context.Background() connPool := pool.NewConnPool(&pool.Options{ Dialer: dummyDialer, PoolSize: int32(32), MaxConcurrentDials: 32, PoolTimeout: time.Second, DialTimeout: time.Second, ConnMaxIdleTime: time.Hour, MinIdleConns: int32(0), }) defer connPool.Close() b.ResetTimer() b.ReportAllocs() // Test with different levels of concurrency concurrencyLevels := []int{1, 2, 4, 8, 16, 32, 64} for _, concurrency := range concurrencyLevels { b.Run(fmt.Sprintf("Concurrency_%d", concurrency), func(b *testing.B) { b.SetParallelism(concurrency) b.RunParallel(func(pb *testing.PB) { for pb.Next() { cn, err := connPool.Get(ctx) if err != nil { b.Fatal(err) } connPool.Put(ctx, cn) } }) }) } } // ============================================================================= // PUBSUB BENCHMARKS // ============================================================================= // benchmarkClient creates a Redis client for benchmarking with mock dialer func benchmarkClient(poolSize int) *redis.Client { return redis.NewClient(&redis.Options{ Addr: "localhost:6379", // Mock address DialTimeout: time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, PoolSize: poolSize, MinIdleConns: 0, // Start with no idle connections for consistent benchmarks }) } // BenchmarkPubSubCreation benchmarks PubSub creation and subscription func BenchmarkPubSubCreation(b *testing.B) { ctx := context.Background() poolSizes := []int{1, 4, 8, 16, 32} for _, poolSize := range poolSizes { b.Run(fmt.Sprintf("PoolSize_%d", poolSize), func(b *testing.B) { client := benchmarkClient(poolSize) defer client.Close() b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { pubsub := client.Subscribe(ctx, "test-channel") pubsub.Close() } }) } } // BenchmarkPubSubPatternCreation benchmarks PubSub pattern subscription func BenchmarkPubSubPatternCreation(b *testing.B) { ctx := context.Background() poolSizes := []int{1, 4, 8, 16, 32} for _, poolSize := range poolSizes { b.Run(fmt.Sprintf("PoolSize_%d", poolSize), func(b *testing.B) { client := benchmarkClient(poolSize) defer client.Close() b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { pubsub := client.PSubscribe(ctx, "test-*") pubsub.Close() } }) } } // BenchmarkPubSubConcurrentCreation benchmarks concurrent PubSub creation func BenchmarkPubSubConcurrentCreation(b *testing.B) { ctx := context.Background() client := benchmarkClient(32) defer client.Close() concurrencyLevels := []int{1, 2, 4, 8, 16} for _, concurrency := range concurrencyLevels { b.Run(fmt.Sprintf("Concurrency_%d", concurrency), func(b *testing.B) { b.ResetTimer() b.ReportAllocs() var wg sync.WaitGroup semaphore := make(chan struct{}, concurrency) for i := 0; i < b.N; i++ { wg.Add(1) semaphore <- struct{}{} go func() { defer wg.Done() defer func() { <-semaphore }() pubsub := client.Subscribe(ctx, "test-channel") pubsub.Close() }() } wg.Wait() }) } } // BenchmarkPubSubMultipleChannels benchmarks subscribing to multiple channels func BenchmarkPubSubMultipleChannels(b *testing.B) { ctx := context.Background() client := benchmarkClient(16) defer client.Close() channelCounts := []int{1, 5, 10, 25, 50, 100} for _, channelCount := range channelCounts { b.Run(fmt.Sprintf("Channels_%d", channelCount), func(b *testing.B) { // Prepare channel names channels := make([]string, channelCount) for i := 0; i < channelCount; i++ { channels[i] = fmt.Sprintf("channel-%d", i) } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { pubsub := client.Subscribe(ctx, channels...) pubsub.Close() } }) } } // BenchmarkPubSubReuse benchmarks reusing PubSub connections func BenchmarkPubSubReuse(b *testing.B) { ctx := context.Background() client := benchmarkClient(16) defer client.Close() b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { // Benchmark just the creation and closing of PubSub connections // This simulates reuse patterns without requiring actual Redis operations pubsub := client.Subscribe(ctx, fmt.Sprintf("test-channel-%d", i)) pubsub.Close() } } // ============================================================================= // COMBINED BENCHMARKS // ============================================================================= // BenchmarkPoolAndPubSubMixed benchmarks mixed pool stats and PubSub operations func BenchmarkPoolAndPubSubMixed(b *testing.B) { ctx := context.Background() client := benchmarkClient(32) defer client.Close() b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { // Mix of pool stats collection and PubSub creation if pb.Next() { // Pool stats operation stats := client.PoolStats() _ = stats.Hits + stats.Misses // Use the stats to prevent optimization } if pb.Next() { // PubSub operation pubsub := client.Subscribe(ctx, "test-channel") pubsub.Close() } } }) } // BenchmarkPoolStatsCollection benchmarks pool statistics collection func BenchmarkPoolStatsCollection(b *testing.B) { client := benchmarkClient(16) defer client.Close() b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { stats := client.PoolStats() _ = stats.Hits + stats.Misses + stats.Timeouts // Use the stats to prevent optimization } } // BenchmarkPoolHighContention tests pool performance under high contention func BenchmarkPoolHighContention(b *testing.B) { ctx := context.Background() client := benchmarkClient(32) defer client.Close() b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { // High contention Get/Put operations pubsub := client.Subscribe(ctx, "test-channel") pubsub.Close() } }) }