From e0c006a9b0224ba6a346663724f9f8660321d5f3 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 6 Dec 2011 08:15:45 -0800 Subject: [PATCH] strconv: 34% to 63% faster conversions (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 | 119 ++++++++++++++++++++++++++--------- src/pkg/strconv/itoa_test.go | 38 ++++++++++- 2 files changed, 126 insertions(+), 31 deletions(-) diff --git a/src/pkg/strconv/itoa.go b/src/pkg/strconv/itoa.go index 29a1a81d869..794ef370b26 100644 --- a/src/pkg/strconv/itoa.go +++ b/src/pkg/strconv/itoa.go @@ -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 + } + + } 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 } diff --git a/src/pkg/strconv/itoa_test.go b/src/pkg/strconv/itoa_test.go index 99be968fff7..e0213ae9afe 100644 --- a/src/pkg/strconv/itoa_test.go +++ b/src/pkg/strconv/itoa_test.go @@ -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) + } + } +}