-
Notifications
You must be signed in to change notification settings - Fork 454
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
Make commitlog msgpack encoder really, really fast. #1160
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1160 +/- ##
========================================
- Coverage 71.6% 71.5% -0.1%
========================================
Files 725 726 +1
Lines 60644 60839 +195
========================================
+ Hits 43422 43518 +96
- Misses 14459 14535 +76
- Partials 2763 2786 +23
Continue to review full report at Codecov.
|
// any performance gains that can be had in this function are worth it. | ||
// | ||
// Before modifying this function, please run the BenchmarkLogEntryEncoderFast benchmark as a small | ||
// degration in this functions performance can have a substantial impact on M3DB. |
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.
degradation
typo
@@ -129,6 +130,7 @@ func newCommitLogWriter( | |||
sizeBuffer: make([]byte, binary.MaxVarintLen64), | |||
seen: bitset.NewBitSet(defaultBitSetLength), | |||
logEncoder: msgpack.NewEncoder(), | |||
logEncoderBuff: make([]byte, 0, 16384), |
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.
Nit: make size a const (I know this was me hah).
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.
Haha will fix
var benchmarkBuf []byte | ||
|
||
func BenchmarkLogEntryEncoderFast(b *testing.B) { | ||
var ( |
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.
N1
|
||
// encodeVarUint64 is used in EncodeLogEntryFast which is a very hot path. | ||
// As a result, many of the function calls in this function have been | ||
// manually inlined to reduce function call overhead. |
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.
So I’ve read this code a few times, the manual inlining makes a lot of sense. Having said that it might be worth using Genny to code gen the code so that if we really need to change parts of the code it affects every party (DRY it up).
Genny should be able to code gen all the inlined parts, it’s more work for sure, so I’m fine if with TODO it but we should definitely do this at some point (otherwise the copy paste manual inlining thing may become a pattern and code gen is definitely better than what manual copy paste to inline things).
Thoughts?
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.
Yeah I thought about doing that too, but I’d prefer to leave as a TODO for now. Mainly because:
- Hopefully this doesn’t become a pattern for us
- This is really unplanned work and there’s like 5 other things I’m supposed to be doing that are not performance improvements and I have a feeling getting the genny thing right will take a little bit of back and forth
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.
This PR makes sense only if you believe Go won't inline your function in the near future. Try debugging this the explanation here in the Go compiler: https://github.com/golang/go/blob/master/src/cmd/compile/internal/gc/inl.go#L5-L25.
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.
I think the benchmarks demonstrate that this P.R is worth it. Even if Golang inlining gets better in the near-future, this function is so hot and the number of function calls we removed so high (we're talking about literally dozens of function calls per iteration that have been removed) I highly doubt that the Go compiler will be able to achieve a similar effect anytime soon
|
||
// encodeBytesLen is not used, it is left here to demonstrate what a manually inlined | ||
// version of this function should look like. | ||
func encodeBytesLen(b []byte, l int) []byte { |
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.
Is this called? Looks Inlined elsewhere. This is where codgen would help (some being reused while others being used as real calls, everything can just always be inlined potentially).
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.
Nope not called (if it is, it’s because I forgot to in-line it). I just left these in as “documention” per the comment
2d095f0
to
8fc4f8a
Compare
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.
LGTM
w.metadataEncoderBuff = w.metadataEncoderBuff[:0] | ||
var err error | ||
|
||
w.metadataEncoderBuff, err = msgpack.EncodeLogMetadataFast(w.metadataEncoderBuff, metadata) |
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.
p.s. I realized this can be collapsed somewhat to do the [:0]
just as you pass it to the EncodeLogMetadataFast method:
var err error
w.metadataEncoderBuff, err = msgpack.EncodeLogMetadataFast(w.metadataEncoderBuff[:0], metadata)
w.logEncoderBuff = w.logEncoderBuff[:0] | ||
|
||
var err error | ||
w.logEncoderBuff, err = msgpack.EncodeLogEntryFast(w.logEncoderBuff, logEntry) |
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.
Same here, can collapse to w.logEncoderBuff, err = msgpack.EncodeLogEntryFast(w.logEncoderBuff[:0], logEntry)
The basic gist of this P.R is that the old code had too many function calls. Function call overhead in Golang is small, but when every function does nothing more than write a few bytes to an array in memory, the function call overhead begins to dominate. As a result, what we do in this P.R is create a fast path that manually inlines (read: copy-pastes) a lot of the helper functions. This makes the code more verbose and more difficult to maintain, but we get an over 10x speedup in exchange which is worth it as this is the hottest code path in M3DB and bottlenecks everything else.
Old path log entry
New path log entry
Old path log metadata
New path log metadata