From: Vladimir Kuzmin Date: Thu, 8 Mar 2018 03:46:54 +0000 (-0800) Subject: net: optimize IP.String for IPv4 X-Git-Tag: go1.11beta1~1260 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=b2d1cd2ad0dd1e0c84e6c3b2eccf77a5bcb84cdd;p=gostls13.git net: optimize IP.String for IPv4 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 --- diff --git a/src/net/ip.go b/src/net/ip.go index 6b7ba4c23e..a94ff73130 100644 --- a/src/net/ip.go +++ b/src/net/ip.go @@ -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: // - "", 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) diff --git a/src/net/ip_test.go b/src/net/ip_test.go index ad13388dd2..60329e9cfe 100644 --- a/src/net/ip_test.go +++ b/src/net/ip_test.go @@ -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() } }