Skip to content

Commit ee7bf06

Browse files
apocelipesgopherbot
authored andcommitted
time: improve ParseDuration performance for invalid input
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 golang#75521 Change-Id: I0dc9f28c9601b6be07b70d0a98613757d76e2c97 GitHub-Last-Rev: 7372739 GitHub-Pull-Request: golang#75531 Reviewed-on: https://go-review.googlesource.com/c/go/+/705195 Reviewed-by: Michael Knyszek <[email protected]> Reviewed-by: Alan Donovan <[email protected]> Auto-Submit: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent f9e61a9 commit ee7bf06

File tree

2 files changed

+27
-10
lines changed

2 files changed

+27
-10
lines changed

src/time/format.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1602,6 +1602,16 @@ func leadingFraction(s string) (x uint64, scale float64, rem string) {
16021602
return x, scale, s[i:]
16031603
}
16041604

1605+
// parseDurationError describes a problem parsing a duration string.
1606+
type parseDurationError struct {
1607+
message string
1608+
value string
1609+
}
1610+
1611+
func (e *parseDurationError) Error() string {
1612+
return "time: " + e.message + " " + quote(e.value)
1613+
}
1614+
16051615
var unitMap = map[string]uint64{
16061616
"ns": uint64(Nanosecond),
16071617
"us": uint64(Microsecond),
@@ -1637,7 +1647,7 @@ func ParseDuration(s string) (Duration, error) {
16371647
return 0, nil
16381648
}
16391649
if s == "" {
1640-
return 0, errors.New("time: invalid duration " + quote(orig))
1650+
return 0, &parseDurationError{"invalid duration", orig}
16411651
}
16421652
for s != "" {
16431653
var (
@@ -1649,13 +1659,13 @@ func ParseDuration(s string) (Duration, error) {
16491659

16501660
// The next character must be [0-9.]
16511661
if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
1652-
return 0, errors.New("time: invalid duration " + quote(orig))
1662+
return 0, &parseDurationError{"invalid duration", orig}
16531663
}
16541664
// Consume [0-9]*
16551665
pl := len(s)
16561666
v, s, err = leadingInt(s)
16571667
if err != nil {
1658-
return 0, errors.New("time: invalid duration " + quote(orig))
1668+
return 0, &parseDurationError{"invalid duration", orig}
16591669
}
16601670
pre := pl != len(s) // whether we consumed anything before a period
16611671

@@ -1669,7 +1679,7 @@ func ParseDuration(s string) (Duration, error) {
16691679
}
16701680
if !pre && !post {
16711681
// no digits (e.g. ".s" or "-.s")
1672-
return 0, errors.New("time: invalid duration " + quote(orig))
1682+
return 0, &parseDurationError{"invalid duration", orig}
16731683
}
16741684

16751685
// Consume unit.
@@ -1681,17 +1691,17 @@ func ParseDuration(s string) (Duration, error) {
16811691
}
16821692
}
16831693
if i == 0 {
1684-
return 0, errors.New("time: missing unit in duration " + quote(orig))
1694+
return 0, &parseDurationError{"missing unit in duration", orig}
16851695
}
16861696
u := s[:i]
16871697
s = s[i:]
16881698
unit, ok := unitMap[u]
16891699
if !ok {
1690-
return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig))
1700+
return 0, &parseDurationError{"unknown unit " + quote(u) + " in duration", orig}
16911701
}
16921702
if v > 1<<63/unit {
16931703
// overflow
1694-
return 0, errors.New("time: invalid duration " + quote(orig))
1704+
return 0, &parseDurationError{"invalid duration", orig}
16951705
}
16961706
v *= unit
16971707
if f > 0 {
@@ -1700,19 +1710,19 @@ func ParseDuration(s string) (Duration, error) {
17001710
v += uint64(float64(f) * (float64(unit) / scale))
17011711
if v > 1<<63 {
17021712
// overflow
1703-
return 0, errors.New("time: invalid duration " + quote(orig))
1713+
return 0, &parseDurationError{"invalid duration", orig}
17041714
}
17051715
}
17061716
d += v
17071717
if d > 1<<63 {
1708-
return 0, errors.New("time: invalid duration " + quote(orig))
1718+
return 0, &parseDurationError{"invalid duration", orig}
17091719
}
17101720
}
17111721
if neg {
17121722
return -Duration(d), nil
17131723
}
17141724
if d > 1<<63-1 {
1715-
return 0, errors.New("time: invalid duration " + quote(orig))
1725+
return 0, &parseDurationError{"invalid duration", orig}
17161726
}
17171727
return Duration(d), nil
17181728
}

src/time/time_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,6 +1620,13 @@ func BenchmarkParseDuration(b *testing.B) {
16201620
}
16211621
}
16221622

1623+
func BenchmarkParseDurationError(b *testing.B) {
1624+
for i := 0; i < b.N; i++ {
1625+
ParseDuration("9223372036854775810ns") // overflow
1626+
ParseDuration("9007199254.740993") // missing unit
1627+
}
1628+
}
1629+
16231630
func BenchmarkHour(b *testing.B) {
16241631
t := Now()
16251632
for i := 0; i < b.N; i++ {

0 commit comments

Comments
 (0)