]> Cypherpunks repositories - gostls13.git/commitdiff
net/netip: optimize parseIPv4 and refactor IPv6 embedded IPv4 parsing
authoraimuz <mr.imuz@gmail.com>
Sat, 18 Nov 2023 03:35:07 +0000 (03:35 +0000)
committerGopher Robot <gobot@golang.org>
Mon, 19 Feb 2024 20:51:11 +0000 (20:51 +0000)
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: 917f78ce4ef2a4156d0291c36047689de1764c3f
GitHub-Pull-Request: golang/go#64219
Reviewed-on: https://go-review.googlesource.com/c/go/+/543179
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Than McIntosh <thanm@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Commit-Queue: Ian Lance Taylor <iant@golang.org>
Auto-Submit: Ian Lance Taylor <iant@golang.org>

src/net/netip/netip.go
src/net/netip/netip_test.go

index 1494fb2413f0b4268b35222c2ebd5cdc8eb6f3b9..ce498a20fdfdbe1dc185302a4c8257a238d0f7f5 100644 (file)
@@ -152,44 +152,53 @@ func (err parseAddrError) Error() string {
        return "ParseAddr(" + q(err.in) + "): " + err.msg
 }
 
-// parseIPv4 parses s as an IPv4 address (in form "192.168.0.1").
-func parseIPv4(s string) (ip Addr, err error) {
-       var fields [4]uint8
+func parseIPv4Fields(in string, off, end int, fields []uint8) error {
        var val, pos int
        var digLen int // number of digits in current octet
+       s := in[off:end]
        for i := 0; i < len(s); i++ {
                if s[i] >= '0' && s[i] <= '9' {
                        if digLen == 1 && val == 0 {
-                               return Addr{}, parseAddrError{in: s, msg: "IPv4 field has octet with leading zero"}
+                               return parseAddrError{in: in, msg: "IPv4 field has octet with leading zero"}
                        }
                        val = val*10 + int(s[i]) - '0'
                        digLen++
                        if val > 255 {
-                               return Addr{}, parseAddrError{in: s, msg: "IPv4 field has value >255"}
+                               return parseAddrError{in: in, msg: "IPv4 field has value >255"}
                        }
                } else if s[i] == '.' {
                        // .1.2.3
                        // 1.2.3.
                        // 1..2.3
                        if i == 0 || i == len(s)-1 || s[i-1] == '.' {
-                               return Addr{}, parseAddrError{in: s, msg: "IPv4 field must have at least one digit", at: s[i:]}
+                               return parseAddrError{in: in, msg: "IPv4 field must have at least one digit", at: s[i:]}
                        }
                        // 1.2.3.4.5
                        if pos == 3 {
-                               return Addr{}, parseAddrError{in: s, msg: "IPv4 address too long"}
+                               return parseAddrError{in: in, msg: "IPv4 address too long"}
                        }
                        fields[pos] = uint8(val)
                        pos++
                        val = 0
                        digLen = 0
                } else {
-                       return Addr{}, parseAddrError{in: s, msg: "unexpected character", at: s[i:]}
+                       return parseAddrError{in: in, msg: "unexpected character", at: s[i:]}
                }
        }
        if pos < 3 {
-               return Addr{}, parseAddrError{in: s, msg: "IPv4 address too short"}
+               return parseAddrError{in: in, msg: "IPv4 address too short"}
        }
        fields[3] = uint8(val)
+       return nil
+}
+
+// parseIPv4 parses s as an IPv4 address (in form "192.168.0.1").
+func parseIPv4(s string) (ip Addr, err error) {
+       var fields [4]uint8
+       err = parseIPv4Fields(s, 0, len(s), fields[:])
+       if err != nil {
+               return Addr{}, err
+       }
        return AddrFrom4(fields), nil
 }
 
@@ -262,17 +271,15 @@ func parseIPv6(in string) (Addr, error) {
                                // Not enough room.
                                return Addr{}, parseAddrError{in: in, msg: "too many hex fields to fit an embedded IPv4 at the end of the address", at: s}
                        }
-                       // TODO: could make this a bit faster by having a helper
-                       // that parses to a [4]byte, and have both parseIPv4 and
-                       // parseIPv6 use it.
-                       ip4, err := parseIPv4(s)
+
+                       end := len(in)
+                       if len(zone) > 0 {
+                               end -= len(zone) + 1
+                       }
+                       err := parseIPv4Fields(in, end-len(s), end, ip[i:i+4])
                        if err != nil {
-                               return Addr{}, parseAddrError{in: in, msg: err.Error(), at: s}
+                               return Addr{}, err
                        }
-                       ip[i] = ip4.v4(0)
-                       ip[i+1] = ip4.v4(1)
-                       ip[i+2] = ip4.v4(2)
-                       ip[i+3] = ip4.v4(3)
                        s = ""
                        i += 4
                        break
index c914c5f256b168fd27342afab33ff2225da993c5..a4ba533343cd754c122a6a0d5c159f4e49f03d41 100644 (file)
@@ -60,7 +60,12 @@ func TestParseAddr(t *testing.T) {
                // 4-in-6 with octet with leading zero
                {
                        in:      "::ffff:1.2.03.4",
-                       wantErr: `ParseAddr("::ffff:1.2.03.4"): ParseAddr("1.2.03.4"): IPv4 field has octet with leading zero (at "1.2.03.4")`,
+                       wantErr: `ParseAddr("::ffff:1.2.03.4"): IPv4 field has octet with leading zero`,
+               },
+               // 4-in-6 with octet with unexpected character
+               {
+                       in:      "::ffff:1.2.3.z",
+                       wantErr: `ParseAddr("::ffff:1.2.3.z"): unexpected character (at "z")`,
                },
                // Basic zero IPv6 address.
                {