Commit 258ed3f2 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

database/sql: avoiding fmt.Sprintf while scanning, avoid allocs with RawBytes

A user reported heavy contention on fmt's printer cache. Avoid
fmt.Sprint. We have to do reflection anyway, and there was
already an asString function to use strconv, so use it.

This CL also eliminates a redundant allocation + copy when
scanning into *[]byte (avoiding the intermediate string)
and avoids an extra alloc when assigning to a caller's RawBytes
(trying to reuse the caller's memory).

Fixes #7086

R=golang-codereviews, nightlyone
CC=golang-codereviews
https://golang.org/cl/50240044
parent 3b8dfc32
......@@ -160,27 +160,19 @@ func convertAssign(dest, src interface{}) error {
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
*d = fmt.Sprintf("%v", src)
*d = asString(src)
return nil
}
case *[]byte:
sv = reflect.ValueOf(src)
switch sv.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
*d = []byte(fmt.Sprintf("%v", src))
if b, ok := asBytes(nil, sv); ok {
*d = b
return nil
}
case *RawBytes:
sv = reflect.ValueOf(src)
switch sv.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
*d = RawBytes(fmt.Sprintf("%v", src))
if b, ok := asBytes([]byte(*d)[:0], sv); ok {
*d = RawBytes(b)
return nil
}
case *bool:
......@@ -271,5 +263,37 @@ func asString(src interface{}) string {
case []byte:
return string(v)
}
rv := reflect.ValueOf(src)
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(rv.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(rv.Uint(), 10)
case reflect.Float64:
return strconv.FormatFloat(rv.Float(), 'g', -1, 64)
case reflect.Float32:
return strconv.FormatFloat(rv.Float(), 'g', -1, 32)
case reflect.Bool:
return strconv.FormatBool(rv.Bool())
}
return fmt.Sprintf("%v", src)
}
func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) {
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.AppendInt(buf, rv.Int(), 10), true
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.AppendUint(buf, rv.Uint(), 10), true
case reflect.Float32:
return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 32), true
case reflect.Float64:
return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 64), true
case reflect.Bool:
return strconv.AppendBool(buf, rv.Bool()), true
case reflect.String:
s := rv.String()
return append(buf, s...), true
}
return
}
......@@ -279,3 +279,51 @@ func TestValueConverters(t *testing.T) {
}
}
}
// Tests that assigning to RawBytes doesn't allocate (and also works).
func TestRawBytesAllocs(t *testing.T) {
buf := make(RawBytes, 10)
test := func(name string, in interface{}, want string) {
if err := convertAssign(&buf, in); err != nil {
t.Fatalf("%s: convertAssign = %v", name, err)
}
match := len(buf) == len(want)
if match {
for i, b := range buf {
if want[i] != b {
match = false
break
}
}
}
if !match {
t.Fatalf("%s: got %q (len %d); want %q (len %d)", name, buf, len(buf), want, len(want))
}
}
n := testing.AllocsPerRun(100, func() {
test("uint64", uint64(12345678), "12345678")
test("uint32", uint32(1234), "1234")
test("uint16", uint16(12), "12")
test("uint8", uint8(1), "1")
test("uint", uint(123), "123")
test("int", int(123), "123")
test("int8", int8(1), "1")
test("int16", int16(12), "12")
test("int32", int32(1234), "1234")
test("int64", int64(12345678), "12345678")
test("float32", float32(1.5), "1.5")
test("float64", float64(64), "64")
test("bool", false, "false")
})
if n > 0.5 {
t.Fatalf("allocs = %v; want 0", n)
}
// This one involves a convT2E allocation, string -> interface{}
n = testing.AllocsPerRun(100, func() {
test("string", "foo", "foo")
})
if n > 1.5 {
t.Fatalf("allocs = %v; want max 1", n)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment