Skip to content

time: improve ParseDuration performance for invalid input  #75521

@apocelipes

Description

@apocelipes

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolNeedsFixThe path to resolution is known, but the work has not been done.Performance

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions