-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use Time.AppendFormat when possible #786
Conversation
Fixes #783. We can take advantage of Time.AppendFormat by adding a new exported method to the JSON encoder, and upcasting when encoding time to use the append-based method. This avoids an allocation to convert the time to a string before appending to the buffer. The benchmarks use the production config, which uses a nanos encoder so to understand the performance difference, I tweaked `BenchmarkZapJSON` to use RFC3339TimeEncoder and ran benchmarks: ``` > benchcmp -best old new benchmark old ns/op new ns/op delta BenchmarkZapJSON-12 514 497 -3.31% benchmark old allocs new allocs delta BenchmarkZapJSON-12 5 4 -20.00% benchmark old bytes new bytes delta BenchmarkZapJSON-12 1297 1265 -2.47% ``` I also wrote a benchmark that only logs a simple message using the RFC3339TimeEncoder, ``` func BenchmarkTimeEncoder(b *testing.B) { cfg := NewProductionConfig().EncoderConfig cfg.EncodeTime = zapcore.RFC3339TimeEncoder logger := New( zapcore.NewCore( zapcore.NewJSONEncoder(cfg), &ztest.Discarder{}, DebugLevel, )) b.ResetTimer() for i := 0; i < b.N; i++ { logger.Info("test") } } ``` Results: ``` > benchcmp -best old new benchmark old ns/op new ns/op delta BenchmarkTimeEncoder-12 695 571 -17.84% benchmark old allocs new allocs delta BenchmarkTimeEncoder-12 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkTimeEncoder-12 32 0 -100.00% ```
Codecov Report
@@ Coverage Diff @@
## master #786 +/- ##
==========================================
+ Coverage 98.21% 98.22% +0.01%
==========================================
Files 43 43
Lines 2293 2306 +13
==========================================
+ Hits 2252 2265 +13
Misses 33 33
Partials 8 8
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/per-formatted/pre-formatted/
} | ||
|
||
func encodeTimeLayout(t time.Time, layout string, enc PrimitiveArrayEncoder) { | ||
if enc, ok := enc.(appendTimeEncoder); ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fwiw: it's possible to just use an anonymous duck, ok := val.(interface { quack() })
in cases like this where aliasing the interfaces serves no other purpose
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, although the line looks kind of long and complicated.
However, I moved the interface into the method, since it's not needed outside of this method.
In #786, we added support for improving the performance of encoding time by allowing encoder implementations to implement AppendTimeLayout. If the encoder implements, `AppendTimeLayout(time.Time, string)`, some of the time encoders shipped with Zap will use that method, which in turn can make use of `Buffer.AppendTime`. That change inadvertently broke the JSON output for arrays of time that make use of that functionality. Compare the old version of AppendTimeLayout with AppendString (both operations append a string to the array). func (enc *jsonEncoder) AppendString(val string) { enc.addElementSeparator() enc.buf.AppendByte('"') // ... } func (enc *jsonEncoder) AppendTimeLayout(time time.Time, layout string) { enc.buf.AppendByte('"') // ... } Without the `enc.addElementSeparator` call, `AppendTimeLayout` does not include the `,` separator between array elements, instead yielding the following. ["2001-12-19T00:00:00Z""2002-12-18T00:00:00Z""2003-12-17T00:00:00Z"] Fixes #798
In #786, we added support for improving the performance of encoding time by allowing encoder implementations to implement AppendTimeLayout. If the encoder implements, `AppendTimeLayout(time.Time, string)`, some of the time encoders shipped with Zap will use that method, which in turn can make use of `Buffer.AppendTime`. That change inadvertently broke the JSON output for arrays of time that make use of that functionality. Compare the old version of AppendTimeLayout with AppendString (both operations append a string to the array). func (enc *jsonEncoder) AppendString(val string) { enc.addElementSeparator() enc.buf.AppendByte('"') // ... } func (enc *jsonEncoder) AppendTimeLayout(time time.Time, layout string) { enc.buf.AppendByte('"') // ... } Without the `enc.addElementSeparator` call, `AppendTimeLayout` does not include the `,` separator between array elements, instead yielding the following. ["2001-12-19T00:00:00Z""2002-12-18T00:00:00Z""2003-12-17T00:00:00Z"] Fixes #798
Fixes uber-go#783. We can take advantage of Time.AppendFormat by adding a new exported method to the JSON encoder, and upcasting when encoding time to use the append-based method. This avoids an allocation to convert the time to a string before appending to the buffer. The benchmarks use the production config, which uses a nanos encoder so to understand the performance difference, I tweaked `BenchmarkZapJSON` to use RFC3339TimeEncoder and ran benchmarks: ``` > benchcmp -best old new benchmark old ns/op new ns/op delta BenchmarkZapJSON-12 514 497 -3.31% benchmark old allocs new allocs delta BenchmarkZapJSON-12 5 4 -20.00% benchmark old bytes new bytes delta BenchmarkZapJSON-12 1297 1265 -2.47% ``` I also wrote a benchmark that only logs a simple message using the RFC3339TimeEncoder, ``` func BenchmarkTimeEncoder(b *testing.B) { cfg := NewProductionConfig().EncoderConfig cfg.EncodeTime = zapcore.RFC3339TimeEncoder logger := New( zapcore.NewCore( zapcore.NewJSONEncoder(cfg), &ztest.Discarder{}, DebugLevel, )) b.ResetTimer() for i := 0; i < b.N; i++ { logger.Info("test") } } ``` Results: ``` > benchcmp -best old new benchmark old ns/op new ns/op delta BenchmarkTimeEncoder-12 695 571 -17.84% benchmark old allocs new allocs delta BenchmarkTimeEncoder-12 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkTimeEncoder-12 32 0 -100.00% ```
In uber-go#786, we added support for improving the performance of encoding time by allowing encoder implementations to implement AppendTimeLayout. If the encoder implements, `AppendTimeLayout(time.Time, string)`, some of the time encoders shipped with Zap will use that method, which in turn can make use of `Buffer.AppendTime`. That change inadvertently broke the JSON output for arrays of time that make use of that functionality. Compare the old version of AppendTimeLayout with AppendString (both operations append a string to the array). func (enc *jsonEncoder) AppendString(val string) { enc.addElementSeparator() enc.buf.AppendByte('"') // ... } func (enc *jsonEncoder) AppendTimeLayout(time time.Time, layout string) { enc.buf.AppendByte('"') // ... } Without the `enc.addElementSeparator` call, `AppendTimeLayout` does not include the `,` separator between array elements, instead yielding the following. ["2001-12-19T00:00:00Z""2002-12-18T00:00:00Z""2003-12-17T00:00:00Z"] Fixes uber-go#798
Fixes #783.
We can take advantage of Time.AppendFormat by adding a new exported
method to the JSON encoder, and upcasting when encoding time to use
the append-based method. This avoids an allocation to convert the time
to a string before appending to the buffer.
The benchmarks use the production config, which uses a nanos encoder
so to understand the performance difference, I tweaked
BenchmarkZapJSON
to use RFC3339TimeEncoder and ran benchmarks:I also wrote a benchmark that only logs a simple message using the
RFC3339TimeEncoder,
Results: