]> Cypherpunks repositories - gostls13.git/commitdiff
time: improve ParseDuration performance for invalid input
authorapocelipes <seve3r@outlook.com>
Fri, 19 Sep 2025 10:34:09 +0000 (10:34 +0000)
committerGopher Robot <gobot@golang.org>
Fri, 19 Sep 2025 15:33:55 +0000 (08:33 -0700)
Add "parseDurationError" to reduce memory allocation in the error path of
"ParseDuration" and delay the generation of error messages. This improves
the performance when dealing with invalid input.

The format of the error message remains unchanged.

Benchmarks:

                      │     old      │                 new                 │
                      │    sec/op    │   sec/op     vs base                │
ParseDurationError-10   132.10n ± 4%   45.93n ± 2%  -65.23% (p=0.000 n=10)

                      │     old     │                new                 │
                      │    B/op     │    B/op     vs base                │
ParseDurationError-10   192.00 ± 0%   64.00 ± 0%  -66.67% (p=0.000 n=10)

                      │    old     │                new                 │
                      │ allocs/op  │ allocs/op   vs base                │
ParseDurationError-10   6.000 ± 0%   2.000 ± 0%  -66.67% (p=0.000 n=10)

Fixes #75521

Change-Id: I0dc9f28c9601b6be07b70d0a98613757d76e2c97
GitHub-Last-Rev: 737273936ad0cafd710fa58d99333416e71e657e
GitHub-Pull-Request: golang/go#75531
Reviewed-on: https://go-review.googlesource.com/c/go/+/705195
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/time/format.go
src/time/time_test.go

index 87e990d48a4fa0548af6d67952fcf1f7e3a18e0b..ad5486f4d28f8930b37b01ec6e0957302395956e 100644 (file)
@@ -1602,6 +1602,16 @@ func leadingFraction(s string) (x uint64, scale float64, rem string) {
        return x, scale, s[i:]
 }
 
+// parseDurationError describes a problem parsing a duration string.
+type parseDurationError struct {
+       message string
+       value   string
+}
+
+func (e *parseDurationError) Error() string {
+       return "time: " + e.message + " " + quote(e.value)
+}
+
 var unitMap = map[string]uint64{
        "ns": uint64(Nanosecond),
        "us": uint64(Microsecond),
@@ -1637,7 +1647,7 @@ func ParseDuration(s string) (Duration, error) {
                return 0, nil
        }
        if s == "" {
-               return 0, errors.New("time: invalid duration " + quote(orig))
+               return 0, &parseDurationError{"invalid duration", orig}
        }
        for s != "" {
                var (
@@ -1649,13 +1659,13 @@ func ParseDuration(s string) (Duration, error) {
 
                // The next character must be [0-9.]
                if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
-                       return 0, errors.New("time: invalid duration " + quote(orig))
+                       return 0, &parseDurationError{"invalid duration", orig}
                }
                // Consume [0-9]*
                pl := len(s)
                v, s, err = leadingInt(s)
                if err != nil {
-                       return 0, errors.New("time: invalid duration " + quote(orig))
+                       return 0, &parseDurationError{"invalid duration", orig}
                }
                pre := pl != len(s) // whether we consumed anything before a period
 
@@ -1669,7 +1679,7 @@ func ParseDuration(s string) (Duration, error) {
                }
                if !pre && !post {
                        // no digits (e.g. ".s" or "-.s")
-                       return 0, errors.New("time: invalid duration " + quote(orig))
+                       return 0, &parseDurationError{"invalid duration", orig}
                }
 
                // Consume unit.
@@ -1681,17 +1691,17 @@ func ParseDuration(s string) (Duration, error) {
                        }
                }
                if i == 0 {
-                       return 0, errors.New("time: missing unit in duration " + quote(orig))
+                       return 0, &parseDurationError{"missing unit in duration", orig}
                }
                u := s[:i]
                s = s[i:]
                unit, ok := unitMap[u]
                if !ok {
-                       return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig))
+                       return 0, &parseDurationError{"unknown unit " + quote(u) + " in duration", orig}
                }
                if v > 1<<63/unit {
                        // overflow
-                       return 0, errors.New("time: invalid duration " + quote(orig))
+                       return 0, &parseDurationError{"invalid duration", orig}
                }
                v *= unit
                if f > 0 {
@@ -1700,19 +1710,19 @@ func ParseDuration(s string) (Duration, error) {
                        v += uint64(float64(f) * (float64(unit) / scale))
                        if v > 1<<63 {
                                // overflow
-                               return 0, errors.New("time: invalid duration " + quote(orig))
+                               return 0, &parseDurationError{"invalid duration", orig}
                        }
                }
                d += v
                if d > 1<<63 {
-                       return 0, errors.New("time: invalid duration " + quote(orig))
+                       return 0, &parseDurationError{"invalid duration", orig}
                }
        }
        if neg {
                return -Duration(d), nil
        }
        if d > 1<<63-1 {
-               return 0, errors.New("time: invalid duration " + quote(orig))
+               return 0, &parseDurationError{"invalid duration", orig}
        }
        return Duration(d), nil
 }
index a2d4305c8c20843fd0abcbc9d9de46616d106388..a453ee043c4985fd7128d27e13864390ed902c32 100644 (file)
@@ -1620,6 +1620,13 @@ func BenchmarkParseDuration(b *testing.B) {
        }
 }
 
+func BenchmarkParseDurationError(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               ParseDuration("9223372036854775810ns") // overflow
+               ParseDuration("9007199254.740993")     // missing unit
+       }
+}
+
 func BenchmarkHour(b *testing.B) {
        t := Now()
        for i := 0; i < b.N; i++ {