...

Source file src/github.com/redis/go-redis/v9/bench_decode_test.go

Documentation: github.com/redis/go-redis/v9

     1  package redis
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/redis/go-redis/v9/internal/proto"
    12  )
    13  
    14  var ctx = context.TODO()
    15  
    16  type ClientStub struct {
    17  	Cmdable
    18  	resp []byte
    19  }
    20  
    21  var initHello = []byte("%1\r\n+proto\r\n:3\r\n")
    22  
    23  func NewClientStub(resp []byte) *ClientStub {
    24  	stub := &ClientStub{
    25  		resp: resp,
    26  	}
    27  
    28  	stub.Cmdable = NewClient(&Options{
    29  		PoolSize: 128,
    30  		Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
    31  			return stub.stubConn(initHello), nil
    32  		},
    33  		DisableIdentity: true,
    34  	})
    35  	return stub
    36  }
    37  
    38  func NewClusterClientStub(resp []byte) *ClientStub {
    39  	stub := &ClientStub{
    40  		resp: resp,
    41  	}
    42  
    43  	client := NewClusterClient(&ClusterOptions{
    44  		PoolSize: 128,
    45  		Addrs:    []string{":6379"},
    46  		Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
    47  			return stub.stubConn(initHello), nil
    48  		},
    49  		DisableIdentity: true,
    50  
    51  		ClusterSlots: func(_ context.Context) ([]ClusterSlot, error) {
    52  			return []ClusterSlot{
    53  				{
    54  					Start: 0,
    55  					End:   16383,
    56  					Nodes: []ClusterNode{{Addr: "127.0.0.1:6379"}},
    57  				},
    58  			}, nil
    59  		},
    60  	})
    61  
    62  	stub.Cmdable = client
    63  	return stub
    64  }
    65  
    66  func (c *ClientStub) stubConn(init []byte) *ConnStub {
    67  	return &ConnStub{
    68  		init: init,
    69  		resp: c.resp,
    70  	}
    71  }
    72  
    73  type ConnStub struct {
    74  	init []byte
    75  	resp []byte
    76  	pos  int
    77  }
    78  
    79  func (c *ConnStub) Read(b []byte) (n int, err error) {
    80  	// Return conn.init()
    81  	if len(c.init) > 0 {
    82  		n = copy(b, c.init)
    83  		c.init = c.init[n:]
    84  		return n, nil
    85  	}
    86  
    87  	if len(c.resp) == 0 {
    88  		return 0, io.EOF
    89  	}
    90  
    91  	if c.pos >= len(c.resp) {
    92  		c.pos = 0
    93  	}
    94  	n = copy(b, c.resp[c.pos:])
    95  	c.pos += n
    96  	return n, nil
    97  }
    98  
    99  func (c *ConnStub) Write(b []byte) (n int, err error)  { return len(b), nil }
   100  func (c *ConnStub) Close() error                       { return nil }
   101  func (c *ConnStub) LocalAddr() net.Addr                { return nil }
   102  func (c *ConnStub) RemoteAddr() net.Addr               { return nil }
   103  func (c *ConnStub) SetDeadline(_ time.Time) error      { return nil }
   104  func (c *ConnStub) SetReadDeadline(_ time.Time) error  { return nil }
   105  func (c *ConnStub) SetWriteDeadline(_ time.Time) error { return nil }
   106  
   107  type ClientStubFunc func([]byte) *ClientStub
   108  
   109  func BenchmarkDecode(b *testing.B) {
   110  	type Benchmark struct {
   111  		name string
   112  		stub ClientStubFunc
   113  	}
   114  
   115  	benchmarks := []Benchmark{
   116  		{"server", NewClientStub},
   117  		{"cluster", NewClusterClientStub},
   118  	}
   119  
   120  	for _, bench := range benchmarks {
   121  		b.Run(fmt.Sprintf("RespError-%s", bench.name), func(b *testing.B) {
   122  			respError(b, bench.stub)
   123  		})
   124  		b.Run(fmt.Sprintf("RespStatus-%s", bench.name), func(b *testing.B) {
   125  			respStatus(b, bench.stub)
   126  		})
   127  		b.Run(fmt.Sprintf("RespInt-%s", bench.name), func(b *testing.B) {
   128  			respInt(b, bench.stub)
   129  		})
   130  		b.Run(fmt.Sprintf("RespString-%s", bench.name), func(b *testing.B) {
   131  			respString(b, bench.stub)
   132  		})
   133  		b.Run(fmt.Sprintf("RespArray-%s", bench.name), func(b *testing.B) {
   134  			respArray(b, bench.stub)
   135  		})
   136  		b.Run(fmt.Sprintf("RespPipeline-%s", bench.name), func(b *testing.B) {
   137  			respPipeline(b, bench.stub)
   138  		})
   139  		b.Run(fmt.Sprintf("RespTxPipeline-%s", bench.name), func(b *testing.B) {
   140  			respTxPipeline(b, bench.stub)
   141  		})
   142  
   143  		// goroutine
   144  		b.Run(fmt.Sprintf("DynamicGoroutine-%s-pool=5", bench.name), func(b *testing.B) {
   145  			dynamicGoroutine(b, bench.stub, 5)
   146  		})
   147  		b.Run(fmt.Sprintf("DynamicGoroutine-%s-pool=20", bench.name), func(b *testing.B) {
   148  			dynamicGoroutine(b, bench.stub, 20)
   149  		})
   150  		b.Run(fmt.Sprintf("DynamicGoroutine-%s-pool=50", bench.name), func(b *testing.B) {
   151  			dynamicGoroutine(b, bench.stub, 50)
   152  		})
   153  		b.Run(fmt.Sprintf("DynamicGoroutine-%s-pool=100", bench.name), func(b *testing.B) {
   154  			dynamicGoroutine(b, bench.stub, 100)
   155  		})
   156  
   157  		b.Run(fmt.Sprintf("StaticGoroutine-%s-pool=5", bench.name), func(b *testing.B) {
   158  			staticGoroutine(b, bench.stub, 5)
   159  		})
   160  		b.Run(fmt.Sprintf("StaticGoroutine-%s-pool=20", bench.name), func(b *testing.B) {
   161  			staticGoroutine(b, bench.stub, 20)
   162  		})
   163  		b.Run(fmt.Sprintf("StaticGoroutine-%s-pool=50", bench.name), func(b *testing.B) {
   164  			staticGoroutine(b, bench.stub, 50)
   165  		})
   166  		b.Run(fmt.Sprintf("StaticGoroutine-%s-pool=100", bench.name), func(b *testing.B) {
   167  			staticGoroutine(b, bench.stub, 100)
   168  		})
   169  	}
   170  }
   171  
   172  func respError(b *testing.B, stub ClientStubFunc) {
   173  	rdb := stub([]byte("-ERR test error\r\n"))
   174  	respErr := proto.RedisError("ERR test error")
   175  
   176  	b.ResetTimer()
   177  	for i := 0; i < b.N; i++ {
   178  		if err := rdb.Get(ctx, "key").Err(); err != respErr {
   179  			b.Fatalf("response error, got %q, want %q", err, respErr)
   180  		}
   181  	}
   182  }
   183  
   184  func respStatus(b *testing.B, stub ClientStubFunc) {
   185  	rdb := stub([]byte("+OK\r\n"))
   186  	var val string
   187  
   188  	b.ResetTimer()
   189  	for i := 0; i < b.N; i++ {
   190  		if val = rdb.Set(ctx, "key", "value", 0).Val(); val != "OK" {
   191  			b.Fatalf("response error, got %q, want OK", val)
   192  		}
   193  	}
   194  }
   195  
   196  func respInt(b *testing.B, stub ClientStubFunc) {
   197  	rdb := stub([]byte(":10\r\n"))
   198  	var val int64
   199  
   200  	b.ResetTimer()
   201  	for i := 0; i < b.N; i++ {
   202  		if val = rdb.Incr(ctx, "key").Val(); val != 10 {
   203  			b.Fatalf("response error, got %q, want 10", val)
   204  		}
   205  	}
   206  }
   207  
   208  func respString(b *testing.B, stub ClientStubFunc) {
   209  	rdb := stub([]byte("$5\r\nhello\r\n"))
   210  	var val string
   211  
   212  	b.ResetTimer()
   213  	for i := 0; i < b.N; i++ {
   214  		if val = rdb.Get(ctx, "key").Val(); val != "hello" {
   215  			b.Fatalf("response error, got %q, want hello", val)
   216  		}
   217  	}
   218  }
   219  
   220  func respArray(b *testing.B, stub ClientStubFunc) {
   221  	rdb := stub([]byte("*3\r\n$5\r\nhello\r\n:10\r\n+OK\r\n"))
   222  	var val []interface{}
   223  
   224  	b.ResetTimer()
   225  	for i := 0; i < b.N; i++ {
   226  		if val = rdb.MGet(ctx, "key").Val(); len(val) != 3 {
   227  			b.Fatalf("response error, got len(%d), want len(3)", len(val))
   228  		}
   229  	}
   230  }
   231  
   232  func respPipeline(b *testing.B, stub ClientStubFunc) {
   233  	rdb := stub([]byte("+OK\r\n$5\r\nhello\r\n:1\r\n"))
   234  	var pipe Pipeliner
   235  
   236  	b.ResetTimer()
   237  	for i := 0; i < b.N; i++ {
   238  		pipe = rdb.Pipeline()
   239  		set := pipe.Set(ctx, "key", "value", 0)
   240  		get := pipe.Get(ctx, "key")
   241  		del := pipe.Del(ctx, "key")
   242  		_, err := pipe.Exec(ctx)
   243  		if err != nil {
   244  			b.Fatalf("response error, got %q, want nil", err)
   245  		}
   246  		if set.Val() != "OK" || get.Val() != "hello" || del.Val() != 1 {
   247  			b.Fatal("response error")
   248  		}
   249  	}
   250  }
   251  
   252  func respTxPipeline(b *testing.B, stub ClientStubFunc) {
   253  	rdb := stub([]byte("+OK\r\n+QUEUED\r\n+QUEUED\r\n+QUEUED\r\n*3\r\n+OK\r\n$5\r\nhello\r\n:1\r\n"))
   254  
   255  	b.ResetTimer()
   256  	for i := 0; i < b.N; i++ {
   257  		var set *StatusCmd
   258  		var get *StringCmd
   259  		var del *IntCmd
   260  		_, err := rdb.TxPipelined(ctx, func(pipe Pipeliner) error {
   261  			set = pipe.Set(ctx, "key", "value", 0)
   262  			get = pipe.Get(ctx, "key")
   263  			del = pipe.Del(ctx, "key")
   264  			return nil
   265  		})
   266  		if err != nil {
   267  			b.Fatalf("response error, got %q, want nil", err)
   268  		}
   269  		if set.Val() != "OK" || get.Val() != "hello" || del.Val() != 1 {
   270  			b.Fatal("response error")
   271  		}
   272  	}
   273  }
   274  
   275  func dynamicGoroutine(b *testing.B, stub ClientStubFunc, concurrency int) {
   276  	rdb := stub([]byte("$5\r\nhello\r\n"))
   277  	c := make(chan struct{}, concurrency)
   278  
   279  	b.ResetTimer()
   280  	for i := 0; i < b.N; i++ {
   281  		c <- struct{}{}
   282  		go func() {
   283  			if val := rdb.Get(ctx, "key").Val(); val != "hello" {
   284  				panic(fmt.Sprintf("response error, got %q, want hello", val))
   285  			}
   286  			<-c
   287  		}()
   288  	}
   289  	// Here no longer wait for all goroutines to complete, it will not affect the test results.
   290  	close(c)
   291  }
   292  
   293  func staticGoroutine(b *testing.B, stub ClientStubFunc, concurrency int) {
   294  	rdb := stub([]byte("$5\r\nhello\r\n"))
   295  	c := make(chan struct{}, concurrency)
   296  
   297  	b.ResetTimer()
   298  
   299  	for i := 0; i < concurrency; i++ {
   300  		go func() {
   301  			for {
   302  				_, ok := <-c
   303  				if !ok {
   304  					return
   305  				}
   306  				if val := rdb.Get(ctx, "key").Val(); val != "hello" {
   307  					panic(fmt.Sprintf("response error, got %q, want hello", val))
   308  				}
   309  			}
   310  		}()
   311  	}
   312  	for i := 0; i < b.N; i++ {
   313  		c <- struct{}{}
   314  	}
   315  	close(c)
   316  }
   317  

View as plain text