From 258ed3f2265a41a46e936e884d8afd6e6f646973 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 10 Jan 2014 12:19:36 -0800 Subject: [PATCH] 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 --- src/pkg/database/sql/convert.go | 50 ++++++++++++++++++++-------- src/pkg/database/sql/convert_test.go | 48 ++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 13 deletions(-) diff --git a/src/pkg/database/sql/convert.go b/src/pkg/database/sql/convert.go index c04adde1fc..c0b38a2494 100644 --- a/src/pkg/database/sql/convert.go +++ b/src/pkg/database/sql/convert.go @@ -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 +} diff --git a/src/pkg/database/sql/convert_test.go b/src/pkg/database/sql/convert_test.go index a39c2c54fb..aa0b6f116a 100644 --- a/src/pkg/database/sql/convert_test.go +++ b/src/pkg/database/sql/convert_test.go @@ -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) + } +} -- 2.48.1