]> Cypherpunks repositories - gostls13.git/commitdiff
net: optimize IP.String for IPv4
authorVladimir Kuzmin <vkuzmin@uber.com>
Thu, 8 Mar 2018 03:46:54 +0000 (19:46 -0800)
committerBrad Fitzpatrick <bradfitz@golang.org>
Fri, 9 Mar 2018 17:03:05 +0000 (17:03 +0000)
This is optimization is only for IPv4. It allocates a result buffer and
writes the IPv4 octets as dotted decimal into it before converting
it to a string just once, reducing allocations.

Benchmark shows performance improvement:

name             old time/op    new time/op    delta
IPString/IPv4-8     284ns ± 4%     144ns ± 6%  -49.35%  (p=0.000 n=19+17)
IPString/IPv6-8    1.34µs ± 5%    1.14µs ± 5%  -14.37%  (p=0.000 n=19+20)

name             old alloc/op   new alloc/op   delta
IPString/IPv4-8     24.0B ± 0%     16.0B ± 0%  -33.33%  (p=0.000 n=20+20)
IPString/IPv6-8      232B ± 0%      224B ± 0%   -3.45%  (p=0.000 n=20+20)

name             old allocs/op  new allocs/op  delta
IPString/IPv4-8      3.00 ± 0%      2.00 ± 0%  -33.33%  (p=0.000 n=20+20)
IPString/IPv6-8      12.0 ± 0%      11.0 ± 0%   -8.33%  (p=0.000 n=20+20)

Fixes #24306

Change-Id: I4e2d30d364e78183d55a42907d277744494b6df3
Reviewed-on: https://go-review.googlesource.com/99395
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
src/net/ip.go
src/net/ip_test.go

index 6b7ba4c23ea49ba1b6a453c419563099f2a57701..a94ff7313009b0730db60c265392c5e199090f01 100644 (file)
@@ -260,6 +260,25 @@ func (ip IP) Mask(mask IPMask) IP {
        return out
 }
 
+// ubtoa encodes the string form of the integer v to dst[start:] and
+// returns the number of bytes written to dst. The caller must ensure
+// that dst has sufficient length.
+func ubtoa(dst []byte, start int, v byte) int {
+       if v < 10 {
+               dst[start] = byte(v + '0')
+               return 1
+       } else if v < 100 {
+               dst[start+1] = byte(v%10 + '0')
+               dst[start] = byte(v/10 + '0')
+               return 2
+       }
+
+       dst[start+2] = byte(v%10 + '0')
+       dst[start+1] = byte((v/10)%10 + '0')
+       dst[start] = byte(v/100 + '0')
+       return 3
+}
+
 // String returns the string form of the IP address ip.
 // It returns one of 4 forms:
 //   - "<nil>", if ip has length 0
@@ -275,10 +294,23 @@ func (ip IP) String() string {
 
        // If IPv4, use dotted notation.
        if p4 := p.To4(); len(p4) == IPv4len {
-               return uitoa(uint(p4[0])) + "." +
-                       uitoa(uint(p4[1])) + "." +
-                       uitoa(uint(p4[2])) + "." +
-                       uitoa(uint(p4[3]))
+               const maxIPv4StringLen = len("255.255.255.255")
+               b := make([]byte, maxIPv4StringLen)
+
+               n := ubtoa(b, 0, p4[0])
+               b[n] = '.'
+               n++
+
+               n += ubtoa(b, n, p4[1])
+               b[n] = '.'
+               n++
+
+               n += ubtoa(b, n, p4[2])
+               b[n] = '.'
+               n++
+
+               n += ubtoa(b, n, p4[3])
+               return string(b[:n])
        }
        if len(p) != IPv6len {
                return "?" + hexString(ip)
index ad13388dd27c38c09cfb84034e34454b4b47b366..60329e9cfef1ae05fa6288c8441f0785435ce680 100644 (file)
@@ -252,9 +252,21 @@ var sink string
 func BenchmarkIPString(b *testing.B) {
        testHookUninstaller.Do(uninstallTestHooks)
 
+       b.Run("IPv4", func(b *testing.B) {
+               benchmarkIPString(b, IPv4len)
+       })
+
+       b.Run("IPv6", func(b *testing.B) {
+               benchmarkIPString(b, IPv6len)
+       })
+}
+
+func benchmarkIPString(b *testing.B, size int) {
+       b.ReportAllocs()
+       b.ResetTimer()
        for i := 0; i < b.N; i++ {
                for _, tt := range ipStringTests {
-                       if tt.in != nil {
+                       if tt.in != nil && len(tt.in) == size {
                                sink = tt.in.String()
                        }
                }