Skip to content

Commit b2d1cd2

Browse files
vkuzmin-uberbradfitz
authored andcommitted
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 <[email protected]>
1 parent 5f541b1 commit b2d1cd2

File tree

2 files changed

+49
-5
lines changed

2 files changed

+49
-5
lines changed

src/net/ip.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,25 @@ func (ip IP) Mask(mask IPMask) IP {
260260
return out
261261
}
262262

263+
// ubtoa encodes the string form of the integer v to dst[start:] and
264+
// returns the number of bytes written to dst. The caller must ensure
265+
// that dst has sufficient length.
266+
func ubtoa(dst []byte, start int, v byte) int {
267+
if v < 10 {
268+
dst[start] = byte(v + '0')
269+
return 1
270+
} else if v < 100 {
271+
dst[start+1] = byte(v%10 + '0')
272+
dst[start] = byte(v/10 + '0')
273+
return 2
274+
}
275+
276+
dst[start+2] = byte(v%10 + '0')
277+
dst[start+1] = byte((v/10)%10 + '0')
278+
dst[start] = byte(v/100 + '0')
279+
return 3
280+
}
281+
263282
// String returns the string form of the IP address ip.
264283
// It returns one of 4 forms:
265284
// - "<nil>", if ip has length 0
@@ -275,10 +294,23 @@ func (ip IP) String() string {
275294

276295
// If IPv4, use dotted notation.
277296
if p4 := p.To4(); len(p4) == IPv4len {
278-
return uitoa(uint(p4[0])) + "." +
279-
uitoa(uint(p4[1])) + "." +
280-
uitoa(uint(p4[2])) + "." +
281-
uitoa(uint(p4[3]))
297+
const maxIPv4StringLen = len("255.255.255.255")
298+
b := make([]byte, maxIPv4StringLen)
299+
300+
n := ubtoa(b, 0, p4[0])
301+
b[n] = '.'
302+
n++
303+
304+
n += ubtoa(b, n, p4[1])
305+
b[n] = '.'
306+
n++
307+
308+
n += ubtoa(b, n, p4[2])
309+
b[n] = '.'
310+
n++
311+
312+
n += ubtoa(b, n, p4[3])
313+
return string(b[:n])
282314
}
283315
if len(p) != IPv6len {
284316
return "?" + hexString(ip)

src/net/ip_test.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,21 @@ var sink string
252252
func BenchmarkIPString(b *testing.B) {
253253
testHookUninstaller.Do(uninstallTestHooks)
254254

255+
b.Run("IPv4", func(b *testing.B) {
256+
benchmarkIPString(b, IPv4len)
257+
})
258+
259+
b.Run("IPv6", func(b *testing.B) {
260+
benchmarkIPString(b, IPv6len)
261+
})
262+
}
263+
264+
func benchmarkIPString(b *testing.B, size int) {
265+
b.ReportAllocs()
266+
b.ResetTimer()
255267
for i := 0; i < b.N; i++ {
256268
for _, tt := range ipStringTests {
257-
if tt.in != nil {
269+
if tt.in != nil && len(tt.in) == size {
258270
sink = tt.in.String()
259271
}
260272
}

0 commit comments

Comments
 (0)