Repository: go-redis/redis
Pull request #1726 (2021-04-15 09:42:12)

add command decoding benchmark
Can add more....

```go
BenchmarkDecode/RespError-Client-2                 	 1000000	      1181 ns/op	     228 B/op	       7 allocs/op
BenchmarkDecode/RespStatus-Client-2                	 1000000	      1185 ns/op	     248 B/op	       6 allocs/op
BenchmarkDecode/RespInt-Client-2                   	 1000000	      1078 ns/op	     180 B/op	       5 allocs/op
BenchmarkDecode/RespString-Client-2                	 1000000	      1123 ns/op	     208 B/op	       6 allocs/op
BenchmarkDecode/RespArray-Client-2                 	  784459	      1525 ns/op	     336 B/op	      12 allocs/op
BenchmarkDecode/RespPipeline-Client-2              	  471608	      2591 ns/op	     800 B/op	      22 allocs/op
BenchmarkDecode/RespTxPipeline-Client-2            	  338113	      3528 ns/op	    1176 B/op	      35 allocs/op
BenchmarkDecode/DynamicGoroutine-Client-pool=5-2   	  444393	      2793 ns/op	     208 B/op	       6 allocs/op
BenchmarkDecode/DynamicGoroutine-Client-pool=20-2  	  446067	      2783 ns/op	     208 B/op	       5 allocs/op
BenchmarkDecode/DynamicGoroutine-Client-pool=50-2  	  446502	      2787 ns/op	     207 B/op	       5 allocs/op
BenchmarkDecode/DynamicGoroutine-Client-pool=100-2 	  446889	      2781 ns/op	     207 B/op	       5 allocs/op
BenchmarkDecode/StaticGoroutine-Client-pool=5-2    	 1000000	      1130 ns/op	     207 B/op	       5 allocs/op
BenchmarkDecode/StaticGoroutine-Client-pool=20-2   	 1000000	      1087 ns/op	     208 B/op	       5 allocs/op
BenchmarkDecode/StaticGoroutine-Client-pool=50-2   	 1000000	      1095 ns/op	     207 B/op	       5 allocs/op
BenchmarkDecode/StaticGoroutine-Client-pool=100-2  	 1000000	      1099 ns/op	     207 B/op	       5 allocs/op
BenchmarkDecode/RespError-Cluster-2                	  805978	      1553 ns/op	     228 B/op	       7 allocs/op
BenchmarkDecode/RespStatus-Cluster-2               	  858656	      1460 ns/op	     248 B/op	       6 allocs/op
BenchmarkDecode/RespInt-Cluster-2                  	  926455	      1353 ns/op	     180 B/op	       5 allocs/op
BenchmarkDecode/RespString-Cluster-2               	  900232	      1390 ns/op	     208 B/op	       6 allocs/op
BenchmarkDecode/RespArray-Cluster-2                	  655177	      1886 ns/op	     336 B/op	      12 allocs/op
BenchmarkDecode/RespPipeline-Cluster-2             	  117696	     10202 ns/op	    1344 B/op	      30 allocs/op
BenchmarkDecode/RespTxPipeline-Cluster-2           	   96346	     13027 ns/op	    2016 B/op	      48 allocs/op
BenchmarkDecode/DynamicGoroutine-Cluster-pool=5-2  	  429230	      2697 ns/op	     207 B/op	       5 allocs/op
BenchmarkDecode/DynamicGoroutine-Cluster-pool=20-2 	  465974	      2673 ns/op	     207 B/op	       5 allocs/op
BenchmarkDecode/DynamicGoroutine-Cluster-pool=50-2 	  464558	      2725 ns/op	     207 B/op	       5 allocs/op
BenchmarkDecode/DynamicGoroutine-Cluster-pool=100-2         	  469230	      2699 ns/op	     207 B/op	       5 allocs/op
BenchmarkDecode/StaticGoroutine-Cluster-pool=5-2            	 1000000	      1264 ns/op	     207 B/op	       5 allocs/op
BenchmarkDecode/StaticGoroutine-Cluster-pool=20-2           	 1000000	      1247 ns/op	     208 B/op	       5 allocs/op
BenchmarkDecode/StaticGoroutine-Cluster-pool=50-2           	 1000000	      1213 ns/op	     208 B/op	       5 allocs/op
BenchmarkDecode/StaticGoroutine-Cluster-pool=100-2          	 1000000	      1215 ns/op	     207 B/op	       5 allocs/op
```


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8392d54..484d40f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,120 @@
 # Changelog
 
-> :heart: [**Uptrace.dev** - distributed traces, logs, and errors in one place](https://uptrace.dev)
+> :heart:
+> [**Uptrace.dev** - All-in-one tool to optimize performance and monitor errors & logs](https://uptrace.dev)
 
-See https://redis.uptrace.dev/changelog/
+## v8.8
+
+- To make updating easier, extra modules now have the same version as go-redis does. That means that
+  you need to update your imports:
+
+```
+github.com/go-redis/redis/extra/redisotel -> github.com/go-redis/redis/extra/redisotel/v8
+github.com/go-redis/redis/extra/rediscensus -> github.com/go-redis/redis/extra/rediscensus/v8
+```
+
+## v8.5
+
+- [knadh](https://github.com/knadh) contributed long-awaited ability to scan Redis Hash into a
+  struct:
+
+```go
+err := rdb.HGetAll(ctx, "hash").Scan(&data)
+
+err := rdb.MGet(ctx, "key1", "key2").Scan(&data)
+```
+
+- Please check [redismock](https://github.com/go-redis/redismock) by
+  [monkey92t](https://github.com/monkey92t) if you are looking for mocking Redis Client.
+
+## v8
+
+- All commands require `context.Context` as a first argument, e.g. `rdb.Ping(ctx)`. If you are not
+  using `context.Context` yet, the simplest option is to define global package variable
+  `var ctx = context.TODO()` and use it when `ctx` is required.
+
+- Full support for `context.Context` canceling.
+
+- Added `redis.NewFailoverClusterClient` that supports routing read-only commands to a slave node.
+
+- Added `redisext.OpenTemetryHook` that adds
+  [Redis OpenTelemetry instrumentation](https://redis.uptrace.dev/tracing/).
+
+- Redis slow log support.
+
+- Ring uses Rendezvous Hashing by default which provides better distribution. You need to move
+  existing keys to a new location or keys will be inaccessible / lost. To use old hashing scheme:
+
+```go
+import "github.com/golang/groupcache/consistenthash"
+
+ring := redis.NewRing(&redis.RingOptions{
+    NewConsistentHash: func() {
+        return consistenthash.New(100, crc32.ChecksumIEEE)
+    },
+})
+```
+
+- `ClusterOptions.MaxRedirects` default value is changed from 8 to 3.
+- `Options.MaxRetries` default value is changed from 0 to 3.
+
+- `Cluster.ForEachNode` is renamed to `ForEachShard` for consistency with `Ring`.
+
+## v7.3
+
+- New option `Options.Username` which causes client to use `AuthACL`. Be aware if your connection
+  URL contains username.
+
+## v7.2
+
+- Existing `HMSet` is renamed to `HSet` and old deprecated `HMSet` is restored for Redis 3 users.
+
+## v7.1
+
+- Existing `Cmd.String` is renamed to `Cmd.Text`. New `Cmd.String` implements `fmt.Stringer`
+  interface.
+
+## v7
+
+- _Important_. Tx.Pipeline now returns a non-transactional pipeline. Use Tx.TxPipeline for a
+  transactional pipeline.
+- WrapProcess is replaced with more convenient AddHook that has access to context.Context.
+- WithContext now can not be used to create a shallow copy of the client.
+- New methods ProcessContext, DoContext, and ExecContext.
+- Client respects Context.Deadline when setting net.Conn deadline.
+- Client listens on Context.Done while waiting for a connection from the pool and returns an error
+  when context context is cancelled.
+- Add PubSub.ChannelWithSubscriptions that sends `*Subscription` in addition to `*Message` to allow
+  detecting reconnections.
+- `time.Time` is now marshalled in RFC3339 format. `rdb.Get("foo").Time()` helper is added to parse
+  the time.
+- `SetLimiter` is removed and added `Options.Limiter` instead.
+- `HMSet` is deprecated as of Redis v4.
+
+## v6.15
+
+- Cluster and Ring pipelines process commands for each node in its own goroutine.
+
+## 6.14
+
+- Added Options.MinIdleConns.
+- Added Options.MaxConnAge.
+- PoolStats.FreeConns is renamed to PoolStats.IdleConns.
+- Add Client.Do to simplify creating custom commands.
+- Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers.
+- Lower memory usage.
+
+## v6.13
+
+- Ring got new options called `HashReplicas` and `Hash`. It is recommended to set
+  `HashReplicas = 1000` for better keys distribution between shards.
+- Cluster client was optimized to use much less memory when reloading cluster state.
+- PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout
+  occurres. In most cases it is recommended to use PubSub.Channel instead.
+- Dialer.KeepAlive is set to 5 minutes by default.
+
+## v6.12
+
+- ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis
+  Servers that don't have cluster mode enabled. See
+  https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup
diff --git a/commands.go b/commands.go
index 5b2f4c6..3774633 100644
--- a/commands.go
+++ b/commands.go
@@ -191,6 +191,7 @@ type Cmdable interface {
 	RPopLPush(ctx context.Context, source, destination string) *StringCmd
 	RPush(ctx context.Context, key string, values ...interface{}) *IntCmd
 	RPushX(ctx context.Context, key string, values ...interface{}) *IntCmd
+	LMove(ctx context.Context, source, destination, srcpos, destpos string) *StringCmd
 
 	SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd
 	SCard(ctx context.Context, key string) *IntCmd
@@ -1431,6 +1432,12 @@ func (c cmdable) RPushX(ctx context.Context, key string, values ...interface{})
 	return cmd
 }
 
+func (c cmdable) LMove(ctx context.Context, source, destination, srcpos, destpos string) *StringCmd {
+	cmd := NewStringCmd(ctx, "lmove", source, destination, srcpos, destpos)
+	_ = c(ctx, cmd)
+	return cmd
+}
+
 //------------------------------------------------------------------------------
 
 func (c cmdable) SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd {
diff --git a/extra/rediscensus/go.mod b/extra/rediscensus/go.mod
index f07afdc..a926b4e 100644
--- a/extra/rediscensus/go.mod
+++ b/extra/rediscensus/go.mod
@@ -1,12 +1,11 @@
-module github.com/go-redis/redis/extra/rediscensus
+module github.com/go-redis/redis/extra/rediscensus/v8
 
 go 1.15
 
-replace github.com/go-redis/redis/extra/rediscmd => ../rediscmd
+replace github.com/go-redis/redis/extra/rediscmd/v8 => ../rediscmd
 
 require (
-	github.com/go-redis/redis/extra/rediscmd v0.2.0
-	github.com/go-redis/redis/v8 v8.4.4
-	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
+	github.com/go-redis/redis/extra/rediscmd/v8 v8.8.2
+	github.com/go-redis/redis/v8 v8.8.2
 	go.opencensus.io v0.22.6
 )
diff --git a/extra/rediscensus/go.sum b/extra/rediscensus/go.sum
index 67f4218..aaa94f4 100644
--- a/extra/rediscensus/go.sum
+++ b/extra/rediscensus/go.sum
@@ -16,18 +16,13 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/go-redis/redis/extra/rediscmd v0.2.0 h1:A3bhCsCKsedClEH9/jYlcKqOuBoeeV+H0yDie5t+a6w=
-github.com/go-redis/redis/extra/rediscmd v0.2.0/go.mod h1:Z5bP1EHl9PvWhx/DupfCdZwB0JgOO3aVxWc/PFux+BE=
-github.com/go-redis/redis/v8 v8.3.2/go.mod h1:jszGxBCez8QA1HWSmQxJO9Y82kNibbUmeYhKWrBejTU=
 github.com/go-redis/redis/v8 v8.4.4 h1:fGqgxCTR1sydaKI00oQf3OmkU/DIe/I/fYXvGklCIuc=
 github.com/go-redis/redis/v8 v8.4.4/go.mod h1:nA0bQuF0i5JFx4Ta9RZxGKXFrQ8cRWntra97f0196iY=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -35,15 +30,14 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -53,33 +47,33 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
 github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
+github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
-github.com/onsi/gomega v1.10.4 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U=
 github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
+github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
+github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
-go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 go.opencensus.io v0.22.6 h1:BdkrbWrzDlV9dnbzoP7sfN+dHheJ4J9JOaYxcUDL+ok=
 go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
-go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY=
 go.opentelemetry.io/otel v0.15.0 h1:CZFy2lPhxd4HlhZnYK8gRyDotksO3Ip9rBweY1vVYJw=
 go.opentelemetry.io/otel v0.15.0/go.mod h1:e4GKElweB8W2gWUqbghw0B8t5MCTccc9212eNHnOHwA=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -88,27 +82,27 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
@@ -118,16 +112,19 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
@@ -138,16 +135,15 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
 google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
 google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/extra/rediscensus/rediscensus.go b/extra/rediscensus/rediscensus.go
index 48a7ca6..1ea6a57 100644
--- a/extra/rediscensus/rediscensus.go
+++ b/extra/rediscensus/rediscensus.go
@@ -3,14 +3,18 @@ package rediscensus
 import (
 	"context"
 
-	"github.com/go-redis/redis/extra/rediscmd"
+	"github.com/go-redis/redis/extra/rediscmd/v8"
 	"github.com/go-redis/redis/v8"
 	"go.opencensus.io/trace"
 )
 
 type TracingHook struct{}
 
-var _ redis.Hook = TracingHook{}
+var _ redis.Hook = (*TracingHook)(nil)
+
+func NewTracingHook() *TracingHook {
+	return new(TracingHook)
+}
 
 func (TracingHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
 	ctx, span := trace.StartSpan(ctx, cmd.FullName())
diff --git a/extra/rediscmd/go.mod b/extra/rediscmd/go.mod
index 59d1a17..8cc00e7 100644
--- a/extra/rediscmd/go.mod
+++ b/extra/rediscmd/go.mod
@@ -1,4 +1,4 @@
-module github.com/go-redis/redis/extra/rediscmd
+module github.com/go-redis/redis/extra/rediscmd/v8
 
 go 1.15
 
diff --git a/extra/rediscmd/go.sum b/extra/rediscmd/go.sum
index c11f5f3..964ee70 100644
--- a/extra/rediscmd/go.sum
+++ b/extra/rediscmd/go.sum
@@ -27,13 +27,13 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
 github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
 github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.4 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U=
 github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
+github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
 github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -65,8 +65,8 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -77,8 +77,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
diff --git a/extra/redisotel/go.mod b/extra/redisotel/go.mod
index 07d08ab..f514a06 100644
--- a/extra/redisotel/go.mod
+++ b/extra/redisotel/go.mod
@@ -1,14 +1,14 @@
-module github.com/go-redis/redis/extra/redisotel
+module github.com/go-redis/redis/extra/redisotel/v8
 
 go 1.15
 
 replace github.com/go-redis/redis/v8 => ../..
 
-replace github.com/go-redis/redis/extra/rediscmd => ../rediscmd
+replace github.com/go-redis/redis/extra/rediscmd/v8 => ../rediscmd
 
 require (
-	github.com/go-redis/redis/extra/rediscmd v0.2.0
-	github.com/go-redis/redis/v8 v8.7.1
+	github.com/go-redis/redis/extra/rediscmd/v8 v8.8.2
+	github.com/go-redis/redis/v8 v8.8.2
 	go.opentelemetry.io/otel v0.19.0
 	go.opentelemetry.io/otel/trace v0.19.0
 )
diff --git a/extra/redisotel/redisotel.go b/extra/redisotel/redisotel.go
index cb321ac..3da6113 100644
--- a/extra/redisotel/redisotel.go
+++ b/extra/redisotel/redisotel.go
@@ -3,7 +3,7 @@ package redisotel
 import (
 	"context"
 
-	"github.com/go-redis/redis/extra/rediscmd"
+	"github.com/go-redis/redis/extra/rediscmd/v8"
 	"github.com/go-redis/redis/v8"
 	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/attribute"
@@ -15,7 +15,11 @@ var tracer = otel.Tracer("github.com/go-redis/redis")
 
 type TracingHook struct{}
 
-var _ redis.Hook = TracingHook{}
+var _ redis.Hook = (*TracingHook)(nil)
+
+func NewTracingHook() *TracingHook {
+	return new(TracingHook)
+}
 
 func (TracingHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
 	if !trace.SpanFromContext(ctx).IsRecording() {


Test patch:
diff --git a/benchmark_decode_test.go b/benchmark_decode_test.go
new file mode 100644
index 0000000..dc4c9b4
--- /dev/null
+++ b/benchmark_decode_test.go
@@ -0,0 +1,317 @@
+package redis
+
+import (
+	"context"
+	"fmt"
+	"github.com/go-redis/redis/v8/internal/proto"
+	"net"
+	"testing"
+	"time"
+)
+
+var ctx = context.TODO()
+
+type ConnStub struct {
+	buff []byte
+	idx  int
+}
+
+func (c *ConnStub) Read(b []byte) (n int, err error) {
+	n = copy(b, c.buff[c.idx:])
+	if len(c.buff) > c.idx+n {
+		c.idx += n
+	} else {
+		c.idx = 0
+	}
+	return n, nil
+}
+func (c *ConnStub) Write(b []byte) (n int, err error)  { return len(b), nil }
+func (c *ConnStub) Close() error                       { return nil }
+func (c *ConnStub) LocalAddr() net.Addr                { return nil }
+func (c *ConnStub) RemoteAddr() net.Addr               { return nil }
+func (c *ConnStub) SetDeadline(_ time.Time) error      { return nil }
+func (c *ConnStub) SetReadDeadline(_ time.Time) error  { return nil }
+func (c *ConnStub) SetWriteDeadline(_ time.Time) error { return nil }
+
+type ClientStub struct {
+	Cmdable
+	name string
+	buff []byte
+	conn []*ConnStub
+}
+
+func (stub *ClientStub) SetResponse(b []byte) {
+	stub.buff = make([]byte, len(b))
+	copy(stub.buff, b)
+
+	for _, c := range stub.conn {
+		c.buff = make([]byte, len(b))
+		copy(c.buff, b)
+	}
+}
+
+func (stub *ClientStub) AppendConn(c *ConnStub) {
+	if len(stub.buff) > 0 {
+		c.buff = make([]byte, len(stub.buff))
+		copy(c.buff, stub.buff)
+	}
+	stub.conn = append(stub.conn, c)
+}
+
+func (stub *ClientStub) Name() string {
+	return stub.name
+}
+
+func NewDecodeClient() *ClientStub {
+	stub := &ClientStub{
+		name: "Client",
+	}
+	stub.Cmdable = NewClient(&Options{
+		PoolSize: 128,
+		Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
+			conn := &ConnStub{}
+			stub.AppendConn(conn)
+			return conn, nil
+		},
+	})
+	return stub
+}
+
+func NewDecodeClusterClient() *ClientStub {
+	stub := &ClientStub{
+		name: "Cluster",
+	}
+	client := NewClusterClient(&ClusterOptions{
+		PoolSize: 128,
+		Addrs:    []string{"127.0.0.1:6379"},
+		Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
+			c := &ConnStub{}
+			stub.AppendConn(c)
+			return c, nil
+		},
+		ClusterSlots: func(_ context.Context) ([]ClusterSlot, error) {
+			return []ClusterSlot{
+				{
+					Start: 0,
+					End:   16383,
+					Nodes: []ClusterNode{{Addr: "127.0.0.1:6379"}},
+				},
+			}, nil
+		},
+	})
+	// init command.
+	tmpClient := NewClient(&Options{Addr: ":6379"})
+	cmdCache, err := tmpClient.Command(ctx).Result()
+	_ = tmpClient.Close()
+	client.cmdsInfoCache = newCmdsInfoCache(func(_ context.Context) (map[string]*CommandInfo, error) {
+		return cmdCache, err
+	})
+
+	stub.Cmdable = client
+	return stub
+}
+
+func BenchmarkDecode(b *testing.B) {
+	stubs := []*ClientStub{
+		NewDecodeClient(),
+		NewDecodeClusterClient(),
+	}
+
+	for _, stub := range stubs {
+		b.Run(fmt.Sprintf("RespError-%s", stub.Name()), func(b *testing.B) {
+			respError(b, stub)
+		})
+		b.Run(fmt.Sprintf("RespStatus-%s", stub.Name()), func(b *testing.B) {
+			respStatus(b, stub)
+		})
+		b.Run(fmt.Sprintf("RespInt-%s", stub.Name()), func(b *testing.B) {
+			respInt(b, stub)
+		})
+		b.Run(fmt.Sprintf("RespString-%s", stub.Name()), func(b *testing.B) {
+			respString(b, stub)
+		})
+		b.Run(fmt.Sprintf("RespArray-%s", stub.Name()), func(b *testing.B) {
+			respArray(b, stub)
+		})
+		b.Run(fmt.Sprintf("RespPipeline-%s", stub.Name()), func(b *testing.B) {
+			respPipeline(b, stub)
+		})
+		b.Run(fmt.Sprintf("RespTxPipeline-%s", stub.Name()), func(b *testing.B) {
+			respTxPipeline(b, stub)
+		})
+
+		// goroutine
+		b.Run(fmt.Sprintf("DynamicGoroutine-%s-pool=5", stub.Name()), func(b *testing.B) {
+			dynamicGoroutine(b, stub, 5)
+		})
+		b.Run(fmt.Sprintf("DynamicGoroutine-%s-pool=20", stub.Name()), func(b *testing.B) {
+			dynamicGoroutine(b, stub, 20)
+		})
+		b.Run(fmt.Sprintf("DynamicGoroutine-%s-pool=50", stub.Name()), func(b *testing.B) {
+			dynamicGoroutine(b, stub, 50)
+		})
+		b.Run(fmt.Sprintf("DynamicGoroutine-%s-pool=100", stub.Name()), func(b *testing.B) {
+			dynamicGoroutine(b, stub, 100)
+		})
+
+		b.Run(fmt.Sprintf("StaticGoroutine-%s-pool=5", stub.Name()), func(b *testing.B) {
+			staticGoroutine(b, stub, 5)
+		})
+		b.Run(fmt.Sprintf("StaticGoroutine-%s-pool=20", stub.Name()), func(b *testing.B) {
+			staticGoroutine(b, stub, 20)
+		})
+		b.Run(fmt.Sprintf("StaticGoroutine-%s-pool=50", stub.Name()), func(b *testing.B) {
+			staticGoroutine(b, stub, 50)
+		})
+		b.Run(fmt.Sprintf("StaticGoroutine-%s-pool=100", stub.Name()), func(b *testing.B) {
+			staticGoroutine(b, stub, 100)
+		})
+	}
+}
+
+func respError(b *testing.B, stub *ClientStub) {
+	stub.SetResponse([]byte("-ERR test error\r\n"))
+	respErr := proto.RedisError("ERR test error")
+	var err error
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if err = stub.Get(ctx, "key").Err(); err != respErr {
+			b.Fatalf("response error, got %q, want %q", err, respErr)
+		}
+	}
+}
+
+func respStatus(b *testing.B, stub *ClientStub) {
+	stub.SetResponse([]byte("+OK\r\n"))
+	var val string
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if val = stub.Set(ctx, "key", "value", 0).Val(); val != "OK" {
+			b.Fatalf("response error, got %q, want OK", val)
+		}
+	}
+}
+
+func respInt(b *testing.B, stub *ClientStub) {
+	stub.SetResponse([]byte(":10\r\n"))
+	var val int64
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if val = stub.Incr(ctx, "key").Val(); val != 10 {
+			b.Fatalf("response error, got %q, want 10", val)
+		}
+	}
+}
+
+func respString(b *testing.B, stub *ClientStub) {
+	stub.SetResponse([]byte("$5\r\nhello\r\n"))
+	var val string
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if val = stub.Get(ctx, "key").Val(); val != "hello" {
+			b.Fatalf("response error, got %q, want hello", val)
+		}
+	}
+}
+
+func respArray(b *testing.B, stub *ClientStub) {
+	stub.SetResponse([]byte("*3\r\n$5\r\nhello\r\n:10\r\n+OK\r\n"))
+	var val []interface{}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if val = stub.MGet(ctx, "key").Val(); len(val) != 3 {
+			b.Fatalf("response error, got len(%d), want len(3)", len(val))
+		}
+	}
+}
+
+func respPipeline(b *testing.B, stub *ClientStub) {
+	stub.SetResponse([]byte("+OK\r\n$5\r\nhello\r\n:1\r\n"))
+	var pipe Pipeliner
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		pipe = stub.Pipeline()
+		set := pipe.Set(ctx, "key", "value", 0)
+		get := pipe.Get(ctx, "key")
+		del := pipe.Del(ctx, "key")
+		_, err := pipe.Exec(ctx)
+		if err != nil {
+			b.Fatalf("response error, got %q, want nil", err)
+		}
+		if set.Val() != "OK" || get.Val() != "hello" || del.Val() != 1 {
+			b.Fatal("response error")
+		}
+	}
+}
+
+func respTxPipeline(b *testing.B, stub *ClientStub) {
+	stub.SetResponse([]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"))
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		var set *StatusCmd
+		var get *StringCmd
+		var del *IntCmd
+		_, err := stub.TxPipelined(ctx, func(pipe Pipeliner) error {
+			set = pipe.Set(ctx, "key", "value", 0)
+			get = pipe.Get(ctx, "key")
+			del = pipe.Del(ctx, "key")
+			return nil
+		})
+		if err != nil {
+			b.Fatalf("response error, got %q, want nil", err)
+		}
+		if set.Val() != "OK" || get.Val() != "hello" || del.Val() != 1 {
+			b.Fatal("response error")
+		}
+	}
+}
+
+func dynamicGoroutine(b *testing.B, stub *ClientStub, concurrency int) {
+	stub.SetResponse([]byte("$5\r\nhello\r\n"))
+	c := make(chan struct{}, concurrency)
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		c <- struct{}{}
+		go func() {
+			if val := stub.Get(ctx, "key").Val(); val != "hello" {
+				panic(fmt.Sprintf("response error, got %q, want hello", val))
+			}
+			<-c
+		}()
+	}
+	// Here no longer wait for all goroutines to complete, it will not affect the test results.
+	close(c)
+}
+
+func staticGoroutine(b *testing.B, stub *ClientStub, concurrency int) {
+	stub.SetResponse([]byte("$5\r\nhello\r\n"))
+	c := make(chan struct{}, concurrency)
+
+	b.ResetTimer()
+
+	for i := 0; i < concurrency; i++ {
+		go func() {
+			for {
+				_, ok := <-c
+				if !ok {
+					return
+				}
+				if val := stub.Get(ctx, "key").Val(); val != "hello" {
+					panic(fmt.Sprintf("response error, got %q, want hello", val))
+				}
+			}
+		}()
+	}
+	for i := 0; i < b.N; i++ {
+		c <- struct{}{}
+	}
+	close(c)
+}
diff --git a/commands_test.go b/commands_test.go
index 313d08c..e927c58 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -2317,6 +2317,28 @@ var _ = Describe("Commands", func() {
 			Expect(lRange.Err()).NotTo(HaveOccurred())
 			Expect(lRange.Val()).To(Equal([]string{}))
 		})
+
+		It("should LMove", func() {
+			rPush := client.RPush(ctx, "lmove1", "ichi")
+			Expect(rPush.Err()).NotTo(HaveOccurred())
+			Expect(rPush.Val()).To(Equal(int64(1)))
+
+			rPush = client.RPush(ctx, "lmove1", "ni")
+			Expect(rPush.Err()).NotTo(HaveOccurred())
+			Expect(rPush.Val()).To(Equal(int64(2)))
+
+			rPush = client.RPush(ctx, "lmove1", "san")
+			Expect(rPush.Err()).NotTo(HaveOccurred())
+			Expect(rPush.Val()).To(Equal(int64(3)))
+
+			lMove := client.LMove(ctx, "lmove1", "lmove2", "RIGHT", "LEFT")
+			Expect(lMove.Err()).NotTo(HaveOccurred())
+			Expect(lMove.Val()).To(Equal("san"))
+
+			lRange := client.LRange(ctx, "lmove2", 0, -1)
+			Expect(lRange.Err()).NotTo(HaveOccurred())
+			Expect(lRange.Val()).To(Equal([]string{"san"}))
+		})
 	})
 
 	Describe("sets", func() {