Benchmark fairness: zap’s hot path for dynamic fields #1576
Replies: 2 comments
|
I'm a maintainer, but this usage looks idiomatic enough - very basic, but idiomatic. The only difference I found was that https://github.com/uber-go/zap/blob/master/benchmarks/zap_test.go#L109C26-L109C47 uses a different time encoder which per https://github.com/uber-go/zap/blob/v1.28.0/zapcore/encoder.go#L119 appears to be slightly faster. I doubt it will move the needle enough though. Perhaps your benchmarks should contain asserts on the exact output, to verify no silly things like different time encoding is happening? |
|
Thanks. That answers the question I came here with: the current public API path is a reasonable, idiomatic representation of Zap. |
Uh oh!
There was an error while loading. Please reload this page.
Hi zap maintainers and community!
I maintain HaloLog and its reproducible multi-logger benchmark suite. Before treating the comparison as settled, I want zap v1.27.0 represented by a configuration its maintainers consider technically fair.
The benchmark constructs zap as follows, quoted verbatim from the suite:
func newZap() *zap.Logger { return zap.New(zapcore.NewCore( zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(io.Discard), zap.InfoLevel, )) }The manual core is deliberate. zap.NewProduction() enables 100/100 sampling by default, which would drop repeated benchmark records. It also enables work such as caller capture that is not included for the other loggers. The benchmark therefore uses the production JSON encoder without those additional behaviors.
Each comparative operation emits a complete structured JSON record containing a timestamp, level, message, and equivalent field values. The suite measures single-goroutine encoding and dispatch to io.Discard, not disk or network throughput.
For call-site fields, the timed operations use zap’s typed API:
l.Info(msg, zap.String("key", "value"))l.Info(msg, zap.Int("k1", 1), zap.Int("k2", 2), // ... zap.Int("k10", 10), )The request-scoped scenario binds five fields once with Logger.With(...), then adds one call-site field per record.
The twenty-field scenario constructs its []zap.Field slice before the timer starts. Caller and stack-trace capture are disabled, and the writer is intentionally not wrapped with zapcore.Lock. These choices are documented as favorable to zap.
My remaining question is narrow:
For fields whose values are available only at the call site, is Logger.Info(msg, zap.Int(...)) the idiomatic public hot path you would want represented? If not, is there another public pattern that preserves the same timestamp, level, message, and dynamic-field semantics with less per-call work?
I would add any proposed lower-level or specialized path as a separate result rather than silently replacing the public Logger.Info measurement. If an equivalent pattern exists, I will add it, rerun the suite, and publish both results.
Benchmark source:
https://github.com/Go-Gen-Ecosystem/halolog/blob/main/benchmarks/comparison_bench_test.go
Request-scoped benchmark:
https://github.com/Go-Gen-Ecosystem/halolog/blob/main/benchmarks/context_bench_test.go
Methodology and disclosed biases:
https://github.com/Go-Gen-Ecosystem/halolog/blob/main/benchmarks/comprehensive_comparison.md
Thank you.
All reactions