Reduce allocations performed by the Logger returned from log.With.#28
Reduce allocations performed by the Logger returned from log.With.#28peterbourgon merged 2 commits intogo-kit:masterfrom
Conversation
The old method performed O(n) allocs, the new method is O(1), where n is the number of nested With contexts.
|
I'm not immediately seeing where the |
There was a problem hiding this comment.
Can we keep parameter names consistent? i.e. here it should be keyvals ...interface{}
There was a problem hiding this comment.
Yes, sorry about that. Fixed.
|
@tsenart The problem isn't that Here is some benchmark code that I think represents a typical scenario (let me know if you think this is worth adding to this PR). func BenchmarkTypicalWith(b *testing.B) {
logger := log.NewDiscardLogger()
logger = log.With(logger, "lvl", "info")
logger = log.With(logger, "host", "hostname")
logger = log.With(logger, "svc", "svcname")
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
l := log.With(logger, "client", "client-id", "item", "item-id")
l.Log("step", 1)
l.Log("step", 2, "param", "arg")
l.Log("step", 3, "err", errors.New("error text"))
}
}I get the results shown below which I think are significant, especially in light of Peter's statement in the discussion of PR #16 that he, "... own[s] and operate[s] services that can become CPU bound based on log output ...." In my experience, logging impacts CPU primarily by heap allocations and formatting into the final |
|
@ChrisHines: That makes sense now. Thanks for the detailed explanation. |
There was a problem hiding this comment.
Yes, l.keyvals is capacity sliced when each withLogger is created. See line 33 and 50. The code passes TestWithConcurrent with race detector and multiple cores enabled.
There was a problem hiding this comment.
👍
On Sunday, April 19, 2015, Chris Hines notifications@github.com wrote:
In log/log.go
#28 (comment):
- // Using the extra capacity without copying risks a data race that
- // would violate the Logger interface contract.
- n := len(keyvals)
- return &withLogger{
logger: logger,keyvals: keyvals[:n:n],- }
+}
+type withLogger struct {
- logger Logger
- keyvals []interface{}
+}
+func (l *withLogger) Log(keyvals ...interface{}) error {
- return l.logger.Log(append(l.keyvals, keyvals...)...)
Yes, l.keyvals is capacity sliced when each withLogger is created. See
line 33 and 50. The code passes TestWithConcurrent with race detector and
multiple cores enabled.—
Reply to this email directly or view it on GitHub
https://github.com/peterbourgon/gokit/pull/28/files#r28652359.
Sent from Gmail Mobile
|
Nice! Thanks! |
Reduce allocations performed by the Logger returned from log.With.
Reduce allocations performed by the Logger returned from log.With.
The old method performed O(n) allocs, the new method is O(1), where n is the number of nested With contexts.
(Note: this PR also adds NewDiscardLogger() to the API. It is used in the new benchmarks, but also has other uses, so I thought it should be exported.)