Proposal Details

A common use of time.ParseDuration is to check whether a string is a valid time.Duration. However, when the string is not a valid duration, this function can be slow and allocate more memory:

func BenchmarkParseDurationError(b *testing.B) {
    for i := 0; i < b.N; i++ {
        ParseDuration("9007199254.740993") // missing unit
    }
}

/*
BenchmarkParseDurationError-10          19131199                64.33 ns/op          104 B/op          3 allocs/op
BenchmarkParseDurationError-10          18929782                63.66 ns/op          104 B/op          3 allocs/op
BenchmarkParseDurationError-10          19234929                63.45 ns/op          104 B/op          3 allocs/op
*/

As a comparison, a successful parsing operation takes only 20 - 40ns and requires no memory allocation.

This function runs slowly because it extensively uses code like errors.New(“time: invalid duration ” + quote(orig)) to dynamically allocate memory along the error path. In most cases, the code only checks err != nil and does not use the information within, making these memory allocations unnecessary.

The new ParseDurationError allows the creation of error message strings to be deferred until its Error method is called, thereby reducing memory allocation and providing structured error information.

ParseDurationError could be like:

type ParseDurationError struct {
    Message  string
    Duration string
}

// newParseDurationError creates a new ParseDurationError.
// The provided duration is cloned to avoid escaping.
func newParseDurationError(message, duration string) *ParseDurationError

func (e *ParseDurationError) Error() string

This can make the code nearly 50% faster and use 50% less memory:

BenchmarkParseDurationError-10          40923620                28.11 ns/op           56 B/op          2 allocs/op
BenchmarkParseDurationError-10          42267286                28.22 ns/op           56 B/op          2 allocs/op
BenchmarkParseDurationError-10          42015886                28.33 ns/op           56 B/op          2 allocs/op

Compatibility: - Error messages format won't be changed. All error messages currently follow a simple and fixed format. - The actual type of the return value will be altered. Since errors.New returns an internal type that cannot be directly used in user code, it is unlikely to break existing code.

Comment From: seankhliao

do we need to expose this type? since the caller already has the duration, exposing the type doesn't seem to add much. if we just change the representation of the error, it doesn't need a proposal

Comment From: apocelipes

do we need to expose this type? since the caller already has the duration, exposing the type doesn't seem to add much. if we just change the representation of the error, it doesn't need a proposal

I'm not sure. For me, using internal type parseDurationError is fine.

Comment From: seankhliao

let's just start with an internal type then. feel free to send a CL

Comment From: gabyhelp

Related Issues

Related Code Changes

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: gopherbot

Change https://go.dev/cl/705195 mentions this issue: time: improve ParseDuration performance for invalid input