]> Cypherpunks repositories - gostls13.git/commitdiff
strconv: 34% to 63% faster conversions
authorRobert Griesemer <gri@golang.org>
Tue, 6 Dec 2011 16:15:45 +0000 (08:15 -0800)
committerRobert Griesemer <gri@golang.org>
Tue, 6 Dec 2011 16:15:45 +0000 (08:15 -0800)
(Note that the Int and Uint benchmarks use different test sets
and thus cannot be compared against each other. Int and Uint
conversions are approximately the same speed).

Before (best of 3 runs):
strconv_test.BenchmarkFormatInt    100000    15636 ns/op
strconv_test.BenchmarkAppendInt    100000    18930 ns/op
strconv_test.BenchmarkFormatUint   500000     4392 ns/op
strconv_test.BenchmarkAppendUint   500000     5152 ns/op

After (best of 3 runs):

strconv_test.BenchmarkFormatInt    200000    10070 ns/op (-36%)
strconv_test.BenchmarkAppendInt    200000     7097 ns/op (-63%)
strconv_test.BenchmarkFormatUint  1000000     2893 ns/op (-34%)
strconv_test.BenchmarkAppendUint   500000     2462 ns/op (-52%)

R=r, rsc, r
CC=golang-dev
https://golang.org/cl/5449093

src/pkg/strconv/itoa.go
src/pkg/strconv/itoa_test.go

index 29a1a81d8695be4200ffafc7423384bbc707cb27..794ef370b26a7c52bc7bfde95cf2c992aaa05264 100644 (file)
@@ -6,37 +6,14 @@ package strconv
 
 // FormatUint returns the string representation of i in the given base.
 func FormatUint(i uint64, base int) string {
-       u := i
-       if base < 2 || 36 < base {
-               panic("invalid base " + Itoa(base))
-       }
-       if u == 0 {
-               return "0"
-       }
-
-       // Assemble decimal in reverse order.
-       var buf [64]byte
-       j := len(buf)
-       b := uint64(base)
-       for u > 0 {
-               j--
-               buf[j] = "0123456789abcdefghijklmnopqrstuvwxyz"[u%b]
-               u /= b
-       }
-
-       return string(buf[j:])
+       _, s := formatBits(nil, i, base, false, false)
+       return s
 }
 
 // FormatInt returns the string representation of i in the given base.
 func FormatInt(i int64, base int) string {
-       if i == 0 {
-               return "0"
-       }
-
-       if i < 0 {
-               return "-" + FormatUint(-uint64(i), base)
-       }
-       return FormatUint(uint64(i), base)
+       _, s := formatBits(nil, uint64(i), base, true, false)
+       return s
 }
 
 // Itoa is shorthand for FormatInt(i, 10).
@@ -47,11 +24,95 @@ func Itoa(i int) string {
 // AppendInt appends the string form of the integer i,
 // as generated by FormatInt, to dst and returns the extended buffer.
 func AppendInt(dst []byte, i int64, base int) []byte {
-       return append(dst, FormatInt(i, base)...)
+       dst, _ = formatBits(dst, uint64(i), base, true, true)
+       return dst
 }
 
 // AppendUint appends the string form of the unsigned integer i,
 // as generated by FormatUint, to dst and returns the extended buffer.
 func AppendUint(dst []byte, i uint64, base int) []byte {
-       return append(dst, FormatUint(i, base)...)
+       dst, _ = formatBits(dst, i, base, false, true)
+       return dst
+}
+
+const digits = "0123456789abcdefghijklmnopqrstuvwxyz"
+
+var shifts = [len(digits) + 1]uint{
+       1 << 1: 1,
+       1 << 2: 2,
+       1 << 3: 3,
+       1 << 4: 4,
+       1 << 5: 5,
+}
+
+// formatBits computes the string representation of u in the given base.
+// If signed is set, u is treated as int64 value. If append_ is set, the
+// string is appended to dst and the resulting byte slice is returned as
+// the first result value; otherwise the string is simply returned as the
+// second result value.
+//
+func formatBits(dst []byte, u uint64, base int, signed, append_ bool) (d []byte, s string) {
+       if base < 2 || base > len(digits) {
+               panic("invalid base")
+       }
+       // 2 <= base && base <= len(digits)
+
+       if u == 0 {
+               if append_ {
+                       d = append(dst, '0')
+                       return
+               }
+               s = "0"
+               return
+       }
+
+       var a [64 + 1]byte // +1 for sign of 64bit value in base 2
+       i := len(a)
+
+       x := int64(u)
+       if x < 0 && signed {
+               u = -u
+       }
+
+       // convert bits
+       if base == 10 {
+               // common case: use constant 10 for / and % because
+               // the compiler can optimize it into a multiply+shift
+               for u != 0 {
+                       i--
+                       a[i] = digits[u%10]
+                       u /= 10
+               }
+
+       } else if s := shifts[base]; s > 0 {
+               // base is power of 2: use shifts and masks instead of / and %
+               m := uintptr(1)<<s - 1
+               for u != 0 {
+                       i--
+                       a[i] = digits[uintptr(u)&m]
+                       u >>= s
+               }
+
+       } else {
+               // general case
+               b := uint64(base)
+               for u != 0 {
+                       i--
+                       a[i] = digits[u%b]
+                       u /= b
+               }
+       }
+
+       // add sign, if any
+       if x < 0 && signed {
+               i--
+               a[i] = '-'
+       }
+
+       if append_ {
+               d = append(dst, a[i:]...)
+               return
+       }
+       s = string(a[i:])
+       return
 }
index 99be968fff7ccf6217eaa8dd7950f1fd8ccb830e..e0213ae9afecd4d1540b2644f9a255abaa321cd7 100644 (file)
@@ -77,8 +77,8 @@ func TestItoa(t *testing.T) {
                                t.Errorf("FormatUint(%v, %v) = %v want %v",
                                        test.in, test.base, s, test.out)
                        }
-                       x := AppendUint([]byte("abc"), uint64(test.in), test.base)
-                       if string(x) != "abc"+test.out {
+                       x := AppendUint(nil, uint64(test.in), test.base)
+                       if string(x) != test.out {
                                t.Errorf("AppendUint(%q, %v, %v) = %q want %v",
                                        "abc", uint64(test.in), test.base, x, test.out)
                        }
@@ -124,3 +124,37 @@ func TestUitoa(t *testing.T) {
 
        }
 }
+
+func BenchmarkFormatInt(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               for _, test := range itob64tests {
+                       FormatInt(test.in, test.base)
+               }
+       }
+}
+
+func BenchmarkAppendInt(b *testing.B) {
+       dst := make([]byte, 0, 30)
+       for i := 0; i < b.N; i++ {
+               for _, test := range itob64tests {
+                       AppendInt(dst, test.in, test.base)
+               }
+       }
+}
+
+func BenchmarkFormatUint(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               for _, test := range uitob64tests {
+                       FormatUint(test.in, test.base)
+               }
+       }
+}
+
+func BenchmarkAppendUint(b *testing.B) {
+       dst := make([]byte, 0, 30)
+       for i := 0; i < b.N; i++ {
+               for _, test := range uitob64tests {
+                       AppendUint(dst, test.in, test.base)
+               }
+       }
+}