From 737273936ad0cafd710fa58d99333416e71e657e Mon Sep 17 00:00:00 2001 From: apocelipes Date: Fri, 19 Sep 2025 01:26:28 +0800 Subject: [PATCH] time: improve ParseDuration performance for invalid input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/time/format.go | 30 ++++++++++++++++++++---------- src/time/time_test.go | 7 +++++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/time/format.go b/src/time/format.go index 87e990d48a4fa0..ad5486f4d28f89 100644 --- a/src/time/format.go +++ b/src/time/format.go @@ -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 } diff --git a/src/time/time_test.go b/src/time/time_test.go index a2d4305c8c2084..a453ee043c4985 100644 --- a/src/time/time_test.go +++ b/src/time/time_test.go @@ -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++ {