Skip to content
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

log: Reuse encoders and buffers for Logfmt logger #253

Merged
merged 1 commit into from
May 8, 2016
Merged

log: Reuse encoders and buffers for Logfmt logger #253

merged 1 commit into from
May 8, 2016

Conversation

nussjustin
Copy link
Contributor

@nussjustin nussjustin commented May 6, 2016

This uses sync.Pool to avoid always allocating new encoders and buffers.

Reduces the number of allocations percall to Log by 2, if a value exists
in the pool.

Benchstat (Go 1.6.2, 5 runs each):

name                      old time/op    new time/op    delta
LogfmtLoggerSimple-8         869ns ± 1%     864ns ± 2%     ~     (p=0.175 n=5+5)
LogfmtLoggerContextual-8    1.28µs ± 0%    1.28µs ± 1%     ~     (p=0.603 n=5+5)

name                      old alloc/op   new alloc/op   delta
LogfmtLoggerSimple-8          384B ± 0%      128B ± 0%  -66.67%  (p=0.008 n=5+5)
LogfmtLoggerContextual-8      560B ± 0%      304B ± 0%  -45.71%  (p=0.008 n=5+5)

name                      old allocs/op  new allocs/op  delta
LogfmtLoggerSimple-8          6.00 ± 0%      4.00 ± 0%  -33.33%  (p=0.008 n=5+5)
LogfmtLoggerContextual-8      9.00 ± 0%      7.00 ± 0%  -22.22%  (p=0.008 n=5+5)

Same for Go TIp (devel +0b6e5e3):

name                      old time/op    new time/op    delta
LogfmtLoggerSimple-8         825ns ± 1%     796ns ± 0%   -3.51%  (p=0.008 n=5+5)
LogfmtLoggerContextual-8    1.12µs ± 1%    1.11µs ± 0%   -1.18%  (p=0.008 n=5+5)

name                      old alloc/op   new alloc/op   delta
LogfmtLoggerSimple-8          384B ± 0%      128B ± 0%  -66.67%  (p=0.008 n=5+5)
LogfmtLoggerContextual-8      560B ± 0%      304B ± 0%  -45.71%  (p=0.008 n=5+5)

name                      old allocs/op  new allocs/op  delta
LogfmtLoggerSimple-8          6.00 ± 0%      4.00 ± 0%  -33.33%  (p=0.008 n=5+5)
LogfmtLoggerContextual-8      9.00 ± 0%      7.00 ± 0%  -22.22%  (p=0.008 n=5+5)

Command: go test -bench Logfmt -benchmem -count 5
Tested on a linux/amd64

@ChrisHines
Copy link
Member

Thanks! After a quick read this looks nice. I'll take a careful look soon.

if err != nil {
// Code copied from logfmt.MarshalKeyvals and modified
// to reuse encoders and buffers with sync.Pool and
// call EndRecord
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ChrisHines Do you think it would make sense to have a MarshalKeyvalsTo (name for debate) in the logfmt package that takes an *Encoder instead of allocating a new one? This way MarshalKeyvals could be implemented by calling MarshalKeyvalsTo with a bytes.Buffer and we could avoid having to duplicate the code here.

Something like this

func MarshalKeyvalsTo(enc *Encoder, keyvals ...interface{}) error

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, although I think adding a method to logfmt.Encoder makes more sense.

func (enc *Encoder) EncodeKeyvals(keyvals ...interface{}) error

See: go-logfmt/logfmt@a0ff333.

@ChrisHines
Copy link
Member

@nuss-justin: Please update for the new Encoder.EncodeKeyvals method in the logfmt package.

Also note that I pushed another speed improvement to the logfmt package earlier. See: go-logfmt/logfmt@a5fe813.

@nussjustin
Copy link
Contributor Author

Updated commit to use the new EncodeKeyvals method on *logfmt.Encoder. Also updated the benchmarks.

// only one call to l.w.Write() for each call to Log. We call Write ourself
// with buf.Bytes() instead of using buf.WriteTo(l.w) as WriteTo can call
// Write multiple times.
if _, err := l.w.Write(enc.buf.Bytes()); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last sentence of this comment is incorrect. Please remove.

The current implementation for bytes.Buffer.WriteTo will only make one call to Write. We use buf.Bytes in favor of buf.WriteTo because buf.Bytes is faster. buf.WriteTo updates the read position of buf based on the number of bytes written, while buf.Bytes does not. Since we discard unread data in buf after returning from this method, we don't need to pay the overhead of updating its read position.

@ChrisHines
Copy link
Member

I'm surprised your new CPU benchmark shows no improvement with the new code. Is that right? On my machine (go1.6.2 windows/amd64) I get:

name                      old time/op    new time/op    delta
LogfmtLoggerSimple-4        2.07µs ± 1%    1.80µs ± 1%  -13.12%  (p=0.000 n=10+10)
LogfmtLoggerContextual-4    3.04µs ± 1%    2.74µs ± 1%  -10.00%   (p=0.000 n=9+10)

name                      old alloc/op   new alloc/op   delta
LogfmtLoggerSimple-4          384B ± 0%      128B ± 0%  -66.67%  (p=0.000 n=10+10)
LogfmtLoggerContextual-4      560B ± 0%      304B ± 0%  -45.71%  (p=0.000 n=10+10)

name                      old allocs/op  new allocs/op  delta
LogfmtLoggerSimple-4          6.00 ± 0%      4.00 ± 0%  -33.33%  (p=0.000 n=10+10)
LogfmtLoggerContextual-4      9.00 ± 0%      7.00 ± 0%  -22.22%  (p=0.000 n=10+10)

But that is good news. A 10% improvement is nice. When combined with the recent improvement in the logfmt package the total improvement is very nice:

name                      old time/op    new time/op    delta
LogfmtLoggerSimple-4        2.42µs ± 2%    1.80µs ± 1%  -25.68%  (p=0.000 n=10+10)
LogfmtLoggerContextual-4    3.55µs ± 3%    2.74µs ± 1%  -22.86%  (p=0.000 n=10+10)

This uses sync.Pool to avoid always allocating new encoders and buffers.
Reduces the number of allocations percall to Log by 2, if a value exists
in the pool.
@nussjustin
Copy link
Contributor Author

@ChrisHines Removed the last sentence and updated my benchmarks again. Also added results for current Go Tip. On my machine there's no change in time/op for 1.6.2.

@ChrisHines
Copy link
Member

@nuss-justin Thanks. Good work.

LGTM.

@ChrisHines ChrisHines merged commit bf1de93 into go-kit:master May 8, 2016
@nussjustin nussjustin deleted the logfmt-pool branch May 8, 2016 16:01
@ChrisHines
Copy link
Member

ChrisHines commented May 10, 2016

@ax-nathan I haven't tagged a new release of go-logfmt/logfmt yet, that could be why glide isn't pulling it in for you. Let me know. I will tag a release when I get a chance (within 24h).

@ax-nathan
Copy link

This is the workaround for the bug in glide that trigged my issue: Masterminds/glide#360 (comment)

Good now. Thanks again!

guycook pushed a commit to codelingo/kit that referenced this pull request Oct 12, 2016
log: Reuse encoders and buffers for Logfmt logger
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants