]> Cypherpunks repositories - gostls13.git/commitdiff
database/sql: avoiding fmt.Sprintf while scanning, avoid allocs with RawBytes
authorBrad Fitzpatrick <bradfitz@golang.org>
Fri, 10 Jan 2014 20:19:36 +0000 (12:19 -0800)
committerBrad Fitzpatrick <bradfitz@golang.org>
Fri, 10 Jan 2014 20:19:36 +0000 (12:19 -0800)
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
src/pkg/database/sql/convert_test.go

index c04adde1fc1603dafc35d4cd04696223555b6aa8..c0b38a24940fcade4452df0b5cdae6369420d3df 100644 (file)
@@ -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
+}
index a39c2c54fba68599c2e87ba76ce9345e9608c1b0..aa0b6f116a00c84d6670c2fb9d8de8be22dd3a37 100644 (file)
@@ -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)
+       }
+}