From aea44109cf23946332319ba51a4a373a9de432e6 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 16 Mar 2017 21:13:29 -0700 Subject: [PATCH] strconv: replace small int string table with constant string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reduces memory use yet still provides the significant performance gain seen when using a fast path for small integers. Improvement of this CL comparing to code without fast path: name old time/op new time/op delta FormatIntSmall-8 35.6ns ± 1% 4.5ns ± 1% -87.30% (p=0.008 n=5+5) AppendIntSmall-8 17.4ns ± 1% 9.4ns ± 3% -45.70% (p=0.008 n=5+5) For comparison, here's the improvement before this CL to code without fast path (1% better for FormatIntSmall): name old time/op new time/op delta FormatIntSmall-8 35.6ns ± 1% 4.0ns ± 3% -88.64% (p=0.008 n=5+5) AppendIntSmall-8 17.4ns ± 1% 8.2ns ± 1% -52.80% (p=0.008 n=5+5) Thus, the code in this CL performs slower for small integers using fast path then the prior version, but this is relative to an already very fast version: name old time/op new time/op delta FormatIntSmall-8 4.05ns ± 3% 4.52ns ± 1% +11.81% (p=0.008 n=5+5) AppendIntSmall-8 8.21ns ± 1% 9.45ns ± 3% +15.05% (p=0.008 n=5+5) Measured on 2.3 GHz Intel Core i7 running macOS Sierra 10.12.3. Overall, it's still ~88% faster than without fast path for small integers, so probably worth it as it removes 100 global string slices in favor of a single string. Credits: This is based on the original (but cleaned up) version of the code by Aliaksandr Valialkin (https://go-review.googlesource.com/c/37963/). Change-Id: Icda78679c8c14666d46257894e9fa3d7f35e58b8 Reviewed-on: https://go-review.googlesource.com/38319 Reviewed-by: Martin Möhrmann --- src/strconv/itoa.go | 53 +++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/src/strconv/itoa.go b/src/strconv/itoa.go index 2a21185a6b3..76ca676c5fb 100644 --- a/src/strconv/itoa.go +++ b/src/strconv/itoa.go @@ -4,12 +4,14 @@ package strconv +const fastSmalls = true // enable fast path for small integers + // FormatUint returns the string representation of i in the given base, // for 2 <= base <= 36. The result uses the lower-case letters 'a' to 'z' // for digit values >= 10. func FormatUint(i uint64, base int) string { - if i < uint64(len(smallints)) && base == 10 { - return smallints[i] + if fastSmalls && i < nSmalls && base == 10 { + return small(int(i)) } _, s := formatBits(nil, i, base, false, false) return s @@ -19,8 +21,8 @@ func FormatUint(i uint64, base int) string { // for 2 <= base <= 36. The result uses the lower-case letters 'a' to 'z' // for digit values >= 10. func FormatInt(i int64, base int) string { - if 0 <= i && i < int64(len(smallints)) && base == 10 { - return smallints[i] + if fastSmalls && 0 <= i && i < nSmalls && base == 10 { + return small(int(i)) } _, s := formatBits(nil, uint64(i), base, i < 0, false) return s @@ -34,8 +36,8 @@ 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 { - if 0 <= i && i < int64(len(smallints)) && base == 10 { - return append(dst, smallints[i]...) + if fastSmalls && 0 <= i && i < nSmalls && base == 10 { + return append(dst, small(int(i))...) } dst, _ = formatBits(dst, uint64(i), base, i < 0, true) return dst @@ -44,30 +46,39 @@ func AppendInt(dst []byte, i int64, base int) []byte { // 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 { - if i < uint64(len(smallints)) && base == 10 { - return append(dst, smallints[i]...) + if fastSmalls && i < nSmalls && base == 10 { + return append(dst, small(int(i))...) } dst, _ = formatBits(dst, i, base, false, true) return dst } +// small returns the string for an i with 0 <= i < nSmalls. +func small(i int) string { + off := 0 + if i < 10 { + off = 1 + } + return smallsString[i*2+off : i*2+2] +} + +const nSmalls = 100 + +const smallsString = "00010203040506070809" + + "10111213141516171819" + + "20212223242526272829" + + "30313233343536373839" + + "40414243444546474849" + + "50515253545556575859" + + "60616263646566676869" + + "70717273747576777879" + + "80818283848586878889" + + "90919293949596979899" + const host32bit = ^uint(0)>>32 == 0 const digits = "0123456789abcdefghijklmnopqrstuvwxyz" -var smallints = [...]string{ - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", - "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", - "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", - "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", - "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", - "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", - "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", - "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", - "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", - "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", -} - var shifts = [len(digits) + 1]uint{ 1 << 1: 1, 1 << 2: 2,