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

Make commitlog msgpack encoder really, really fast. #1160

Merged
merged 22 commits into from
Nov 12, 2018

Conversation

richardartoul
Copy link
Contributor

@richardartoul richardartoul commented Nov 7, 2018

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

goos: darwin
goarch: amd64
pkg: github.com/m3db/m3/src/dbnode/persist/fs/msgpack
BenchmarkLogEntryDecoder-8   	 3000000	       611 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/m3db/m3/src/dbnode/persist/fs/msgpack	2.423s
Success: Benchmarks passed.

New path log entry

goos: darwin
goarch: amd64
pkg: github.com/m3db/m3/src/dbnode/persist/fs/msgpack
BenchmarkLogEntryEncoderFast-8   	30000000	        43.9 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/m3db/m3/src/dbnode/persist/fs/msgpack	1.381s
Success: Benchmarks passed.

Old path log metadata

goos: darwin
goarch: amd64
pkg: github.com/m3db/m3/src/dbnode/persist/fs/msgpack
BenchmarkLogMetadataEncoder-8   	 5000000	       235 ns/op	     130 B/op	       0 allocs/op
PASS
ok  	github.com/m3db/m3/src/dbnode/persist/fs/msgpack	1.526s
Success: Benchmarks passed.

New path log metadata

goos: darwin
goarch: amd64
pkg: github.com/m3db/m3/src/dbnode/persist/fs/msgpack
BenchmarkLogMetadataEncoderFast-8   	50000000	        31.9 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/m3db/m3/src/dbnode/persist/fs/msgpack	1.648s
Success: Benchmarks passed.

@codecov
Copy link

codecov bot commented Nov 7, 2018

Codecov Report

Merging #1160 into master will decrease coverage by <.1%.
The diff coverage is 64%.

Impacted file tree graph

@@           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
Flag Coverage Δ
#aggregator 81.6% <ø> (-0.1%) ⬇️
#cluster 85.9% <ø> (ø) ⬆️
#collector 78.1% <ø> (ø) ⬆️
#dbnode 81% <64%> (-0.3%) ⬇️
#m3em 73.2% <ø> (ø) ⬆️
#m3ninx 75.2% <ø> (-0.1%) ⬇️
#m3nsch 51.1% <ø> (ø) ⬆️
#metrics 18.3% <ø> (ø) ⬆️
#msg 74.9% <ø> (-0.3%) ⬇️
#query 64.2% <ø> (-0.1%) ⬇️
#x 75.2% <ø> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 95dbe5a...ab50047. Read the comment docs.

// 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.
Copy link
Contributor

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),
Copy link
Collaborator

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).

Copy link
Contributor Author

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 (
Copy link
Collaborator

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.
Copy link
Collaborator

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?

Copy link
Contributor Author

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:

  1. Hopefully this doesn’t become a pattern for us
  2. 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

Copy link
Contributor

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.

Copy link
Contributor Author

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 {
Copy link
Collaborator

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).

Copy link
Contributor Author

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

Copy link
Collaborator

@robskillington robskillington left a 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)
Copy link
Collaborator

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)
Copy link
Collaborator

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)

@richardartoul richardartoul merged commit a2fab37 into master Nov 12, 2018
@justinjc justinjc deleted the ra/fast-commitlog branch January 7, 2019 19:31
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.

3 participants