Merge pull request #475 from go-redis/fix/reduce-alloc

Reduce number of allocations.
This commit is contained in:
Vladimir Mihailenco 2017-01-13 13:52:40 +02:00 committed by GitHub
commit 6da05abbaa
11 changed files with 158 additions and 92 deletions

View File

@ -37,7 +37,7 @@ func BenchmarkRedisPing(b *testing.B) {
}) })
} }
func BenchmarkRedisSet(b *testing.B) { func BenchmarkRedisSetString(b *testing.B) {
client := benchmarkRedisClient(10) client := benchmarkRedisClient(10)
defer client.Close() defer client.Close()

View File

@ -378,7 +378,7 @@ func (c *ClusterClient) cmdSlotAndNode(state *clusterState, cmd Cmder) (int, *cl
return 0, node, err return 0, node, err
} }
cmdInfo := c.cmds[cmd.arg(0)] cmdInfo := c.cmds[cmd.name()]
firstKey := cmd.arg(cmdFirstKeyPos(cmd, cmdInfo)) firstKey := cmd.arg(cmdFirstKeyPos(cmd, cmdInfo))
slot := hashtag.Slot(firstKey) slot := hashtag.Slot(firstKey)

View File

@ -33,6 +33,7 @@ var (
type Cmder interface { type Cmder interface {
args() []interface{} args() []interface{}
arg(int) string arg(int) string
name() string
readReply(*pool.Conn) error readReply(*pool.Conn) error
setErr(error) setErr(error)
@ -83,7 +84,7 @@ func cmdString(cmd Cmder, val interface{}) string {
} }
func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int { func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int {
switch cmd.arg(0) { switch cmd.name() {
case "eval", "evalsha": case "eval", "evalsha":
if cmd.arg(2) != "0" { if cmd.arg(2) != "0" {
return 3 return 3
@ -92,7 +93,7 @@ func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int {
} }
} }
if info == nil { if info == nil {
internal.Logf("info for cmd=%s not found", cmd.arg(0)) internal.Logf("info for cmd=%s not found", cmd.name())
return -1 return -1
} }
return int(info.FirstKeyPos) return int(info.FirstKeyPos)
@ -126,6 +127,16 @@ func (cmd *baseCmd) arg(pos int) string {
return s return s
} }
func (cmd *baseCmd) name() string {
if len(cmd._args) > 0 {
// Cmd name must be lower cased.
s := internal.ToLower(cmd.arg(0))
cmd._args[0] = s
return s
}
return ""
}
func (cmd *baseCmd) readTimeout() *time.Duration { func (cmd *baseCmd) readTimeout() *time.Duration {
return cmd._readTimeout return cmd._readTimeout
} }
@ -156,7 +167,7 @@ type Cmd struct {
func NewCmd(args ...interface{}) *Cmd { func NewCmd(args ...interface{}) *Cmd {
return &Cmd{ return &Cmd{
baseCmd: newBaseCmd(args), baseCmd: baseCmd{_args: args},
} }
} }
@ -193,8 +204,9 @@ type SliceCmd struct {
} }
func NewSliceCmd(args ...interface{}) *SliceCmd { func NewSliceCmd(args ...interface{}) *SliceCmd {
cmd := newBaseCmd(args) return &SliceCmd{
return &SliceCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *SliceCmd) Val() []interface{} { func (cmd *SliceCmd) Val() []interface{} {
@ -228,8 +240,9 @@ type StatusCmd struct {
} }
func NewStatusCmd(args ...interface{}) *StatusCmd { func NewStatusCmd(args ...interface{}) *StatusCmd {
cmd := newBaseCmd(args) return &StatusCmd{
return &StatusCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *StatusCmd) Val() string { func (cmd *StatusCmd) Val() string {
@ -258,8 +271,9 @@ type IntCmd struct {
} }
func NewIntCmd(args ...interface{}) *IntCmd { func NewIntCmd(args ...interface{}) *IntCmd {
cmd := newBaseCmd(args) return &IntCmd{
return &IntCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *IntCmd) Val() int64 { func (cmd *IntCmd) Val() int64 {
@ -289,10 +303,9 @@ type DurationCmd struct {
} }
func NewDurationCmd(precision time.Duration, args ...interface{}) *DurationCmd { func NewDurationCmd(precision time.Duration, args ...interface{}) *DurationCmd {
cmd := newBaseCmd(args)
return &DurationCmd{ return &DurationCmd{
baseCmd: baseCmd{_args: args},
precision: precision, precision: precision,
baseCmd: cmd,
} }
} }
@ -327,9 +340,8 @@ type TimeCmd struct {
} }
func NewTimeCmd(args ...interface{}) *TimeCmd { func NewTimeCmd(args ...interface{}) *TimeCmd {
cmd := newBaseCmd(args)
return &TimeCmd{ return &TimeCmd{
baseCmd: cmd, baseCmd: baseCmd{_args: args},
} }
} }
@ -364,8 +376,9 @@ type BoolCmd struct {
} }
func NewBoolCmd(args ...interface{}) *BoolCmd { func NewBoolCmd(args ...interface{}) *BoolCmd {
cmd := newBaseCmd(args) return &BoolCmd{
return &BoolCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *BoolCmd) Val() bool { func (cmd *BoolCmd) Val() bool {
@ -414,16 +427,17 @@ func (cmd *BoolCmd) readReply(cn *pool.Conn) error {
type StringCmd struct { type StringCmd struct {
baseCmd baseCmd
val string val []byte
} }
func NewStringCmd(args ...interface{}) *StringCmd { func NewStringCmd(args ...interface{}) *StringCmd {
cmd := newBaseCmd(args) return &StringCmd{
return &StringCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *StringCmd) Val() string { func (cmd *StringCmd) Val() string {
return cmd.val return internal.BytesToString(cmd.val)
} }
func (cmd *StringCmd) Result() (string, error) { func (cmd *StringCmd) Result() (string, error) {
@ -467,7 +481,7 @@ func (cmd *StringCmd) String() string {
} }
func (cmd *StringCmd) readReply(cn *pool.Conn) error { func (cmd *StringCmd) readReply(cn *pool.Conn) error {
cmd.val, cmd.err = cn.Rd.ReadStringReply() cmd.val, cmd.err = cn.Rd.ReadBytesReply()
return cmd.err return cmd.err
} }
@ -480,8 +494,9 @@ type FloatCmd struct {
} }
func NewFloatCmd(args ...interface{}) *FloatCmd { func NewFloatCmd(args ...interface{}) *FloatCmd {
cmd := newBaseCmd(args) return &FloatCmd{
return &FloatCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *FloatCmd) Val() float64 { func (cmd *FloatCmd) Val() float64 {
@ -510,8 +525,9 @@ type StringSliceCmd struct {
} }
func NewStringSliceCmd(args ...interface{}) *StringSliceCmd { func NewStringSliceCmd(args ...interface{}) *StringSliceCmd {
cmd := newBaseCmd(args) return &StringSliceCmd{
return &StringSliceCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *StringSliceCmd) Val() []string { func (cmd *StringSliceCmd) Val() []string {
@ -545,8 +561,9 @@ type BoolSliceCmd struct {
} }
func NewBoolSliceCmd(args ...interface{}) *BoolSliceCmd { func NewBoolSliceCmd(args ...interface{}) *BoolSliceCmd {
cmd := newBaseCmd(args) return &BoolSliceCmd{
return &BoolSliceCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *BoolSliceCmd) Val() []bool { func (cmd *BoolSliceCmd) Val() []bool {
@ -580,8 +597,9 @@ type StringStringMapCmd struct {
} }
func NewStringStringMapCmd(args ...interface{}) *StringStringMapCmd { func NewStringStringMapCmd(args ...interface{}) *StringStringMapCmd {
cmd := newBaseCmd(args) return &StringStringMapCmd{
return &StringStringMapCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *StringStringMapCmd) Val() map[string]string { func (cmd *StringStringMapCmd) Val() map[string]string {
@ -615,8 +633,9 @@ type StringIntMapCmd struct {
} }
func NewStringIntMapCmd(args ...interface{}) *StringIntMapCmd { func NewStringIntMapCmd(args ...interface{}) *StringIntMapCmd {
cmd := newBaseCmd(args) return &StringIntMapCmd{
return &StringIntMapCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *StringIntMapCmd) Val() map[string]int64 { func (cmd *StringIntMapCmd) Val() map[string]int64 {
@ -650,8 +669,9 @@ type ZSliceCmd struct {
} }
func NewZSliceCmd(args ...interface{}) *ZSliceCmd { func NewZSliceCmd(args ...interface{}) *ZSliceCmd {
cmd := newBaseCmd(args) return &ZSliceCmd{
return &ZSliceCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *ZSliceCmd) Val() []Z { func (cmd *ZSliceCmd) Val() []Z {
@ -689,7 +709,7 @@ type ScanCmd struct {
func NewScanCmd(process func(cmd Cmder) error, args ...interface{}) *ScanCmd { func NewScanCmd(process func(cmd Cmder) error, args ...interface{}) *ScanCmd {
return &ScanCmd{ return &ScanCmd{
baseCmd: newBaseCmd(args), baseCmd: baseCmd{_args: args},
process: process, process: process,
} }
} }
@ -738,8 +758,9 @@ type ClusterSlotsCmd struct {
} }
func NewClusterSlotsCmd(args ...interface{}) *ClusterSlotsCmd { func NewClusterSlotsCmd(args ...interface{}) *ClusterSlotsCmd {
cmd := newBaseCmd(args) return &ClusterSlotsCmd{
return &ClusterSlotsCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *ClusterSlotsCmd) Val() []ClusterSlot { func (cmd *ClusterSlotsCmd) Val() []ClusterSlot {
@ -857,8 +878,9 @@ type GeoPosCmd struct {
} }
func NewGeoPosCmd(args ...interface{}) *GeoPosCmd { func NewGeoPosCmd(args ...interface{}) *GeoPosCmd {
cmd := newBaseCmd(args) return &GeoPosCmd{
return &GeoPosCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *GeoPosCmd) Val() []*GeoPos { func (cmd *GeoPosCmd) Val() []*GeoPos {
@ -902,8 +924,9 @@ type CommandsInfoCmd struct {
} }
func NewCommandsInfoCmd(args ...interface{}) *CommandsInfoCmd { func NewCommandsInfoCmd(args ...interface{}) *CommandsInfoCmd {
cmd := newBaseCmd(args) return &CommandsInfoCmd{
return &CommandsInfoCmd{baseCmd: cmd} baseCmd: baseCmd{_args: args},
}
} }
func (cmd *CommandsInfoCmd) Val() map[string]*CommandInfo { func (cmd *CommandsInfoCmd) Val() map[string]*CommandInfo {

View File

@ -74,7 +74,7 @@ func (p *Reader) ReadReply(m MultiBulkParse) (interface{}, error) {
case StatusReply: case StatusReply:
return parseStatusValue(line) return parseStatusValue(line)
case IntReply: case IntReply:
return parseIntValue(line) return parseInt(line[1:], 10, 64)
case StringReply: case StringReply:
return p.readBytesValue(line) return p.readBytesValue(line)
case ArrayReply: case ArrayReply:
@ -96,13 +96,13 @@ func (p *Reader) ReadIntReply() (int64, error) {
case ErrorReply: case ErrorReply:
return 0, ParseErrorReply(line) return 0, ParseErrorReply(line)
case IntReply: case IntReply:
return parseIntValue(line) return parseInt(line[1:], 10, 64)
default: default:
return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line) return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line)
} }
} }
func (p *Reader) ReadBytesReply() ([]byte, error) { func (p *Reader) ReadTmpBytesReply() ([]byte, error) {
line, err := p.ReadLine() line, err := p.ReadLine()
if err != nil { if err != nil {
return nil, err return nil, err
@ -119,8 +119,18 @@ func (p *Reader) ReadBytesReply() ([]byte, error) {
} }
} }
func (r *Reader) ReadBytesReply() ([]byte, error) {
b, err := r.ReadTmpBytesReply()
if err != nil {
return nil, err
}
cp := make([]byte, len(b))
copy(cp, b)
return cp, nil
}
func (p *Reader) ReadStringReply() (string, error) { func (p *Reader) ReadStringReply() (string, error) {
b, err := p.ReadBytesReply() b, err := p.ReadTmpBytesReply()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -128,11 +138,11 @@ func (p *Reader) ReadStringReply() (string, error) {
} }
func (p *Reader) ReadFloatReply() (float64, error) { func (p *Reader) ReadFloatReply() (float64, error) {
s, err := p.ReadStringReply() b, err := p.ReadTmpBytesReply()
if err != nil { if err != nil {
return 0, err return 0, err
} }
return strconv.ParseFloat(s, 64) return parseFloat(b, 64)
} }
func (p *Reader) ReadArrayReply(m MultiBulkParse) (interface{}, error) { func (p *Reader) ReadArrayReply(m MultiBulkParse) (interface{}, error) {
@ -178,12 +188,7 @@ func (p *Reader) ReadScanReply() ([]string, uint64, error) {
return nil, 0, fmt.Errorf("redis: got %d elements in scan reply, expected 2", n) return nil, 0, fmt.Errorf("redis: got %d elements in scan reply, expected 2", n)
} }
s, err := p.ReadStringReply() cursor, err := p.ReadUint()
if err != nil {
return nil, 0, err
}
cursor, err := strconv.ParseUint(s, 10, 64)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@ -222,6 +227,22 @@ func (p *Reader) readBytesValue(line []byte) ([]byte, error) {
return b[:replyLen], nil return b[:replyLen], nil
} }
func (r *Reader) ReadInt() (int64, error) {
b, err := r.ReadTmpBytesReply()
if err != nil {
return 0, err
}
return parseInt(b, 10, 64)
}
func (r *Reader) ReadUint() (uint64, error) {
b, err := r.ReadTmpBytesReply()
if err != nil {
return 0, err
}
return parseUint(b, 10, 64)
}
// -------------------------------------------------------------------- // --------------------------------------------------------------------
func readN(r io.Reader, b []byte, n int) ([]byte, error) { func readN(r io.Reader, b []byte, n int) ([]byte, error) {
@ -280,13 +301,25 @@ func parseStatusValue(line []byte) ([]byte, error) {
return line[1:], nil return line[1:], nil
} }
func parseIntValue(line []byte) (int64, error) {
return strconv.ParseInt(string(line[1:]), 10, 64)
}
func parseArrayLen(line []byte) (int64, error) { func parseArrayLen(line []byte) (int64, error) {
if isNilReply(line) { if isNilReply(line) {
return 0, internal.Nil return 0, internal.Nil
} }
return parseIntValue(line) return parseInt(line[1:], 10, 64)
}
func atoi(b []byte) (int, error) {
return strconv.Atoi(internal.BytesToString(b))
}
func parseInt(b []byte, base int, bitSize int) (int64, error) {
return strconv.ParseInt(internal.BytesToString(b), base, bitSize)
}
func parseUint(b []byte, base int, bitSize int) (uint64, error) {
return strconv.ParseUint(internal.BytesToString(b), base, bitSize)
}
func parseFloat(b []byte, bitSize int) (float64, error) {
return strconv.ParseFloat(internal.BytesToString(b), bitSize)
} }

View File

@ -3,90 +3,89 @@ package proto
import ( import (
"encoding" "encoding"
"fmt" "fmt"
"strconv"
"gopkg.in/redis.v5/internal" "gopkg.in/redis.v5/internal"
) )
func Scan(s string, v interface{}) error { func Scan(b []byte, v interface{}) error {
switch v := v.(type) { switch v := v.(type) {
case nil: case nil:
return internal.RedisError("redis: Scan(nil)") return internal.RedisError("redis: Scan(nil)")
case *string: case *string:
*v = s *v = internal.BytesToString(b)
return nil return nil
case *[]byte: case *[]byte:
*v = []byte(s) *v = b
return nil return nil
case *int: case *int:
var err error var err error
*v, err = strconv.Atoi(s) *v, err = atoi(b)
return err return err
case *int8: case *int8:
n, err := strconv.ParseInt(s, 10, 8) n, err := parseInt(b, 10, 8)
if err != nil { if err != nil {
return err return err
} }
*v = int8(n) *v = int8(n)
return nil return nil
case *int16: case *int16:
n, err := strconv.ParseInt(s, 10, 16) n, err := parseInt(b, 10, 16)
if err != nil { if err != nil {
return err return err
} }
*v = int16(n) *v = int16(n)
return nil return nil
case *int32: case *int32:
n, err := strconv.ParseInt(s, 10, 32) n, err := parseInt(b, 10, 32)
if err != nil { if err != nil {
return err return err
} }
*v = int32(n) *v = int32(n)
return nil return nil
case *int64: case *int64:
n, err := strconv.ParseInt(s, 10, 64) n, err := parseInt(b, 10, 64)
if err != nil { if err != nil {
return err return err
} }
*v = n *v = n
return nil return nil
case *uint: case *uint:
n, err := strconv.ParseUint(s, 10, 64) n, err := parseUint(b, 10, 64)
if err != nil { if err != nil {
return err return err
} }
*v = uint(n) *v = uint(n)
return nil return nil
case *uint8: case *uint8:
n, err := strconv.ParseUint(s, 10, 8) n, err := parseUint(b, 10, 8)
if err != nil { if err != nil {
return err return err
} }
*v = uint8(n) *v = uint8(n)
return nil return nil
case *uint16: case *uint16:
n, err := strconv.ParseUint(s, 10, 16) n, err := parseUint(b, 10, 16)
if err != nil { if err != nil {
return err return err
} }
*v = uint16(n) *v = uint16(n)
return nil return nil
case *uint32: case *uint32:
n, err := strconv.ParseUint(s, 10, 32) n, err := parseUint(b, 10, 32)
if err != nil { if err != nil {
return err return err
} }
*v = uint32(n) *v = uint32(n)
return nil return nil
case *uint64: case *uint64:
n, err := strconv.ParseUint(s, 10, 64) n, err := parseUint(b, 10, 64)
if err != nil { if err != nil {
return err return err
} }
*v = n *v = n
return nil return nil
case *float32: case *float32:
n, err := strconv.ParseFloat(s, 32) n, err := parseFloat(b, 32)
if err != nil { if err != nil {
return err return err
} }
@ -94,13 +93,13 @@ func Scan(s string, v interface{}) error {
return err return err
case *float64: case *float64:
var err error var err error
*v, err = strconv.ParseFloat(s, 64) *v, err = parseFloat(b, 64)
return err return err
case *bool: case *bool:
*v = len(s) == 1 && s[0] == '1' *v = len(b) == 1 && b[0] == '1'
return nil return nil
case encoding.BinaryUnmarshaler: case encoding.BinaryUnmarshaler:
return v.UnmarshalBinary([]byte(s)) return v.UnmarshalBinary(b)
default: default:
return fmt.Errorf( return fmt.Errorf(
"redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v) "redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v)

7
internal/safe.go Normal file
View File

@ -0,0 +1,7 @@
// +build appengine
package internal
func BytesToString(b []byte) string {
return string(b)
}

14
internal/unsafe.go Normal file
View File

@ -0,0 +1,14 @@
// +build !appengine
package internal
import (
"reflect"
"unsafe"
)
func BytesToString(b []byte) string {
bytesHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))
strHeader := reflect.StringHeader{bytesHeader.Data, bytesHeader.Len}
return *(*string)(unsafe.Pointer(&strHeader))
}

View File

@ -13,7 +13,7 @@ func ToLower(s string) string {
} }
b[i] = c b[i] = c
} }
return string(b) return BytesToString(b)
} }
func isLower(s string) bool { func isLower(s string) bool {

View File

@ -386,22 +386,12 @@ func timeParser(rd *proto.Reader, n int64) (interface{}, error) {
return nil, fmt.Errorf("got %d elements, expected 2", n) return nil, fmt.Errorf("got %d elements, expected 2", n)
} }
secStr, err := rd.ReadStringReply() sec, err := rd.ReadInt()
if err != nil { if err != nil {
return nil, err return nil, err
} }
microsecStr, err := rd.ReadStringReply() microsec, err := rd.ReadInt()
if err != nil {
return nil, err
}
sec, err := strconv.ParseInt(secStr, 10, 64)
if err != nil {
return nil, err
}
microsec, err := strconv.ParseInt(microsecStr, 10, 64)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -53,7 +53,7 @@ func NewBoolResult(val bool, err error) *BoolCmd {
// NewStringResult returns a StringCmd initalised with val and err for testing // NewStringResult returns a StringCmd initalised with val and err for testing
func NewStringResult(val string, err error) *StringCmd { func NewStringResult(val string, err error) *StringCmd {
var cmd StringCmd var cmd StringCmd
cmd.val = val cmd.val = []byte(val)
cmd.setErr(err) cmd.setErr(err)
return &cmd return &cmd
} }

View File

@ -265,7 +265,7 @@ func (c *Ring) shardByName(name string) (*ringShard, error) {
} }
func (c *Ring) cmdShard(cmd Cmder) (*ringShard, error) { func (c *Ring) cmdShard(cmd Cmder) (*ringShard, error) {
cmdInfo := c.cmdInfo(cmd.arg(0)) cmdInfo := c.cmdInfo(cmd.name())
firstKey := cmd.arg(cmdFirstKeyPos(cmd, cmdInfo)) firstKey := cmd.arg(cmdFirstKeyPos(cmd, cmdInfo))
return c.shardByKey(firstKey) return c.shardByKey(firstKey)
} }
@ -364,7 +364,7 @@ func (c *Ring) Pipelined(fn func(*Pipeline) error) ([]Cmder, error) {
func (c *Ring) pipelineExec(cmds []Cmder) (firstErr error) { func (c *Ring) pipelineExec(cmds []Cmder) (firstErr error) {
cmdsMap := make(map[string][]Cmder) cmdsMap := make(map[string][]Cmder)
for _, cmd := range cmds { for _, cmd := range cmds {
cmdInfo := c.cmdInfo(cmd.arg(0)) cmdInfo := c.cmdInfo(cmd.name())
name := cmd.arg(cmdFirstKeyPos(cmd, cmdInfo)) name := cmd.arg(cmdFirstKeyPos(cmd, cmdInfo))
if name != "" { if name != "" {
name = c.hash.Get(hashtag.Key(name)) name = c.hash.Get(hashtag.Key(name))