Skip to content

Commit e5f4c68

Browse files
aimuzgopherbot
authored andcommitted
net/netip: optimize parseIPv4 and refactor IPv6 embedded IPv4 parsing
This change refactors the parseIPv4 function to extract a new helper function, parseIPv4Fields, which is now used by both parseIPv4 and parseIPv6 functions. The extraction of this logic into a separate helper function removes code duplication and improves the performance of parsing IPv6 addresses that contain an embedded IPv4 address. Additionally, the error handling within the IP address parsing logic has been streamlined to provide clearer messages when encountering incorrect formats or values in IPv4 fields. Benchmark: ``` benchstat old.out new.out goos: darwin goarch: amd64 pkg: net/netip cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz │ old.out │ new.out │ │ sec/op │ sec/op vs base │ ParseAddr/v4-12 22.23n ± 2% 21.86n ± 2% ~ (p=0.127 n=10) ParseAddr/v6-12 69.67n ± 7% 70.31n ± 1% ~ (p=0.128 n=10) ParseAddr/v6_ellipsis-12 48.22n ± 17% 48.58n ± 1% ~ (p=0.739 n=10) ParseAddr/v6_v4-12 60.73n ± 36% 51.54n ± 1% -15.14% (p=0.000 n=10) ParseAddr/v6_zone-12 102.50n ± 22% 93.50n ± 0% -8.79% (p=0.000 n=10) ParseAddrPort/v4-12 38.07n ± 1% 36.84n ± 2% -3.22% (p=0.000 n=10) ParseAddrPort/v6-12 84.61n ± 1% 87.21n ± 1% +3.07% (p=0.000 n=10) ParseAddrPort/v6_ellipsis-12 69.65n ± 8% 64.56n ± 2% -7.31% (p=0.023 n=10) ParseAddrPort/v6_v4-12 71.88n ± 1% 70.61n ± 1% -1.76% (p=0.000 n=10) ParseAddrPort/v6_zone-12 119.0n ± 2% 118.0n ± 2% ~ (p=0.108 n=10) geomean 62.38n 60.17n -3.54% │ old.out │ new.out │ │ B/op │ B/op vs base │ ParseAddr/v4-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddr/v6-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddr/v6_ellipsis-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddr/v6_v4-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddr/v6_zone-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddrPort/v4-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddrPort/v6-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddrPort/v6_ellipsis-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddrPort/v6_v4-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddrPort/v6_zone-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean │ old.out │ new.out │ │ allocs/op │ allocs/op vs base │ ParseAddr/v4-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddr/v6-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddr/v6_ellipsis-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddr/v6_v4-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddr/v6_zone-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddrPort/v4-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddrPort/v6-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddrPort/v6_ellipsis-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddrPort/v6_v4-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ParseAddrPort/v6_zone-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean ``` Change-Id: I403cb76f449a0bf203a821294df25d3c9031df4c GitHub-Last-Rev: 917f78c GitHub-Pull-Request: #64219 Reviewed-on: https://go-review.googlesource.com/c/go/+/543179 Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Than McIntosh <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Commit-Queue: Ian Lance Taylor <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]>
1 parent 0882ca7 commit e5f4c68

File tree

2 files changed

+31
-19
lines changed

2 files changed

+31
-19
lines changed

src/net/netip/netip.go

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -152,44 +152,53 @@ func (err parseAddrError) Error() string {
152152
return "ParseAddr(" + q(err.in) + "): " + err.msg
153153
}
154154

155-
// parseIPv4 parses s as an IPv4 address (in form "192.168.0.1").
156-
func parseIPv4(s string) (ip Addr, err error) {
157-
var fields [4]uint8
155+
func parseIPv4Fields(in string, off, end int, fields []uint8) error {
158156
var val, pos int
159157
var digLen int // number of digits in current octet
158+
s := in[off:end]
160159
for i := 0; i < len(s); i++ {
161160
if s[i] >= '0' && s[i] <= '9' {
162161
if digLen == 1 && val == 0 {
163-
return Addr{}, parseAddrError{in: s, msg: "IPv4 field has octet with leading zero"}
162+
return parseAddrError{in: in, msg: "IPv4 field has octet with leading zero"}
164163
}
165164
val = val*10 + int(s[i]) - '0'
166165
digLen++
167166
if val > 255 {
168-
return Addr{}, parseAddrError{in: s, msg: "IPv4 field has value >255"}
167+
return parseAddrError{in: in, msg: "IPv4 field has value >255"}
169168
}
170169
} else if s[i] == '.' {
171170
// .1.2.3
172171
// 1.2.3.
173172
// 1..2.3
174173
if i == 0 || i == len(s)-1 || s[i-1] == '.' {
175-
return Addr{}, parseAddrError{in: s, msg: "IPv4 field must have at least one digit", at: s[i:]}
174+
return parseAddrError{in: in, msg: "IPv4 field must have at least one digit", at: s[i:]}
176175
}
177176
// 1.2.3.4.5
178177
if pos == 3 {
179-
return Addr{}, parseAddrError{in: s, msg: "IPv4 address too long"}
178+
return parseAddrError{in: in, msg: "IPv4 address too long"}
180179
}
181180
fields[pos] = uint8(val)
182181
pos++
183182
val = 0
184183
digLen = 0
185184
} else {
186-
return Addr{}, parseAddrError{in: s, msg: "unexpected character", at: s[i:]}
185+
return parseAddrError{in: in, msg: "unexpected character", at: s[i:]}
187186
}
188187
}
189188
if pos < 3 {
190-
return Addr{}, parseAddrError{in: s, msg: "IPv4 address too short"}
189+
return parseAddrError{in: in, msg: "IPv4 address too short"}
191190
}
192191
fields[3] = uint8(val)
192+
return nil
193+
}
194+
195+
// parseIPv4 parses s as an IPv4 address (in form "192.168.0.1").
196+
func parseIPv4(s string) (ip Addr, err error) {
197+
var fields [4]uint8
198+
err = parseIPv4Fields(s, 0, len(s), fields[:])
199+
if err != nil {
200+
return Addr{}, err
201+
}
193202
return AddrFrom4(fields), nil
194203
}
195204

@@ -262,17 +271,15 @@ func parseIPv6(in string) (Addr, error) {
262271
// Not enough room.
263272
return Addr{}, parseAddrError{in: in, msg: "too many hex fields to fit an embedded IPv4 at the end of the address", at: s}
264273
}
265-
// TODO: could make this a bit faster by having a helper
266-
// that parses to a [4]byte, and have both parseIPv4 and
267-
// parseIPv6 use it.
268-
ip4, err := parseIPv4(s)
274+
275+
end := len(in)
276+
if len(zone) > 0 {
277+
end -= len(zone) + 1
278+
}
279+
err := parseIPv4Fields(in, end-len(s), end, ip[i:i+4])
269280
if err != nil {
270-
return Addr{}, parseAddrError{in: in, msg: err.Error(), at: s}
281+
return Addr{}, err
271282
}
272-
ip[i] = ip4.v4(0)
273-
ip[i+1] = ip4.v4(1)
274-
ip[i+2] = ip4.v4(2)
275-
ip[i+3] = ip4.v4(3)
276283
s = ""
277284
i += 4
278285
break

src/net/netip/netip_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ func TestParseAddr(t *testing.T) {
6060
// 4-in-6 with octet with leading zero
6161
{
6262
in: "::ffff:1.2.03.4",
63-
wantErr: `ParseAddr("::ffff:1.2.03.4"): ParseAddr("1.2.03.4"): IPv4 field has octet with leading zero (at "1.2.03.4")`,
63+
wantErr: `ParseAddr("::ffff:1.2.03.4"): IPv4 field has octet with leading zero`,
64+
},
65+
// 4-in-6 with octet with unexpected character
66+
{
67+
in: "::ffff:1.2.3.z",
68+
wantErr: `ParseAddr("::ffff:1.2.3.z"): unexpected character (at "z")`,
6469
},
6570
// Basic zero IPv6 address.
6671
{

0 commit comments

Comments
 (0)