mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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 #75521
Change-Id: I0dc9f28c9601b6be07b70d0a98613757d76e2c97
GitHub-Last-Rev: 737273936a
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>
This commit is contained in:
parent
f9e61a9a32
commit
ee7bf06cb3
2 changed files with 27 additions and 10 deletions
|
|
@ -1602,6 +1602,16 @@ func leadingFraction(s string) (x uint64, scale float64, rem string) {
|
||||||
return x, scale, s[i:]
|
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{
|
var unitMap = map[string]uint64{
|
||||||
"ns": uint64(Nanosecond),
|
"ns": uint64(Nanosecond),
|
||||||
"us": uint64(Microsecond),
|
"us": uint64(Microsecond),
|
||||||
|
|
@ -1637,7 +1647,7 @@ func ParseDuration(s string) (Duration, error) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return 0, errors.New("time: invalid duration " + quote(orig))
|
return 0, &parseDurationError{"invalid duration", orig}
|
||||||
}
|
}
|
||||||
for s != "" {
|
for s != "" {
|
||||||
var (
|
var (
|
||||||
|
|
@ -1649,13 +1659,13 @@ func ParseDuration(s string) (Duration, error) {
|
||||||
|
|
||||||
// The next character must be [0-9.]
|
// The next character must be [0-9.]
|
||||||
if !(s[0] == '.' || '0' <= s[0] && s[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]*
|
// Consume [0-9]*
|
||||||
pl := len(s)
|
pl := len(s)
|
||||||
v, s, err = leadingInt(s)
|
v, s, err = leadingInt(s)
|
||||||
if err != nil {
|
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
|
pre := pl != len(s) // whether we consumed anything before a period
|
||||||
|
|
||||||
|
|
@ -1669,7 +1679,7 @@ func ParseDuration(s string) (Duration, error) {
|
||||||
}
|
}
|
||||||
if !pre && !post {
|
if !pre && !post {
|
||||||
// no digits (e.g. ".s" or "-.s")
|
// no digits (e.g. ".s" or "-.s")
|
||||||
return 0, errors.New("time: invalid duration " + quote(orig))
|
return 0, &parseDurationError{"invalid duration", orig}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consume unit.
|
// Consume unit.
|
||||||
|
|
@ -1681,17 +1691,17 @@ func ParseDuration(s string) (Duration, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i == 0 {
|
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]
|
u := s[:i]
|
||||||
s = s[i:]
|
s = s[i:]
|
||||||
unit, ok := unitMap[u]
|
unit, ok := unitMap[u]
|
||||||
if !ok {
|
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 {
|
if v > 1<<63/unit {
|
||||||
// overflow
|
// overflow
|
||||||
return 0, errors.New("time: invalid duration " + quote(orig))
|
return 0, &parseDurationError{"invalid duration", orig}
|
||||||
}
|
}
|
||||||
v *= unit
|
v *= unit
|
||||||
if f > 0 {
|
if f > 0 {
|
||||||
|
|
@ -1700,19 +1710,19 @@ func ParseDuration(s string) (Duration, error) {
|
||||||
v += uint64(float64(f) * (float64(unit) / scale))
|
v += uint64(float64(f) * (float64(unit) / scale))
|
||||||
if v > 1<<63 {
|
if v > 1<<63 {
|
||||||
// overflow
|
// overflow
|
||||||
return 0, errors.New("time: invalid duration " + quote(orig))
|
return 0, &parseDurationError{"invalid duration", orig}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d += v
|
d += v
|
||||||
if d > 1<<63 {
|
if d > 1<<63 {
|
||||||
return 0, errors.New("time: invalid duration " + quote(orig))
|
return 0, &parseDurationError{"invalid duration", orig}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if neg {
|
if neg {
|
||||||
return -Duration(d), nil
|
return -Duration(d), nil
|
||||||
}
|
}
|
||||||
if d > 1<<63-1 {
|
if d > 1<<63-1 {
|
||||||
return 0, errors.New("time: invalid duration " + quote(orig))
|
return 0, &parseDurationError{"invalid duration", orig}
|
||||||
}
|
}
|
||||||
return Duration(d), nil
|
return Duration(d), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
func BenchmarkHour(b *testing.B) {
|
||||||
t := Now()
|
t := Now()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue