Skip to content

Reduce encoding allocations

Luke Champine requested to merge encoder into master

Follow up to #2282, implementing a similar strategy for reducing allocations. This should have a huge effect on our total number of allocations, and the resulting amount of time we spend in GC. The reason for this is that we hash lots of data, and hashing requires encoding, and encoding requires allocations. The two biggest offenders are encoding.WriteUint64 (for writing length prefixes) and Currency.MarshalSia (which makes a copy of the bytes before writing them). I was able to nullify both of these by writing length prefixes to a reusable buffer and by doing some bitmath on the raw Currency bytes to avoid the copy. The end result is that most encodings require only a single allocation, even for very complex objects.

Microbenchmark:

old:

BenchmarkEncodeEmptyBlock-4	10000000    127 ns/op   500.19 MB/s     4 allocs/op
BenchmarkEncodeHeavyBlock-4	  500000   3330 ns/op   455.85 MB/s   102 allocs/op

new:

BenchmarkEncodeEmptyBlock-4	10000000    127 ns/op   501.06 MB/s     2 allocs/op
BenchmarkEncodeHeavyBlock-4	 1000000   1812 ns/op   837.71 MB/s     2 allocs/op

And a more realistic benchmark, unlocking a fresh wallet:

Master branch: 50.0 GB GB allocated (640 million objects) PR branch: 43.6 GB allocated (470 million objects)

This wasn't as dramatic as I was hoping, but I think it's because rescanning is mostly a decode-heavy operation.

The block encoding already includes sanity checks, and it hasn't complained during testing or during the unlocks. I can sync a full node again just to be sure, though.

Merge request reports