refactor: don't zero-after-free during serialization#31
Draft
refactor: don't zero-after-free during serialization#31
Conversation
Problem: Block benches used `DataStream::Rewind()` to reuse the same input, but `::read()`/`::ignore()` clear the buffer when it is fully consumed, making a rewind after full deserialization observe an empty stream making the benchmarks useless. A dummy write was added originally to prevent this compaction, but was easy to misread as referring to `DataStream::Compact()` - which was in fact unused. Fixes: * Change the deserialize bench inputs to use a lightweight `SpanReader` instead and can be recreated each iteration instead of rewinding. * Remove unused DataStream::Compact(). * Leave `DataStream::Rewind()` usage only in `PrevectorDeserialize` for now (refactored in a follow-up commit). * Deduplicate and document the clear-on-fully-consumed behavior in `::read()`/`::ignore()` by calling clear(). Since the serialization benchmarks avoid `DataStream` (and implicitly `SerializeData` and `zero_after_free_allocator`) after this change, a dedicated benchmark was added to measure allocation and deallocation. | ns/MB | MB/s | err% | total | benchmark |--------------------:|--------------------:|--------:|----------:|:---------- | 17,099.33 | 58,481.82 | 0.4% | 10.94 | `DataStreamInitAndFree`
By integrating rewinding with the other methods we can also use it for non-benchmark code as well. The surrounding code was reformatted for consistency.
`DataStream` is used for non-secret data like P2P messages, block serialization, and database keys/values. Using `zero_after_free_allocator` for this data has been acknowledged as unnecessary since at least 2015 [1]: "network data should never be sensitive" - sipa "i'd prefer if we were more conservative with where that was used. for non-sensitive data, it just seems wasteful" - cfields Benchmarking suggests this barrier may also inhibit other compiler optimizations. For actual secrets, `secure_allocator` should be used instead, which both zeros memory and calls mlock() to prevent paging to disk. Use `std::vector<std::byte>` for `DataStream` storage instead of `SerializeData`. Code that still relies on zeroing now includes `zeroafterfree.h` directly (wallet migration code). This improves `DataStreamInitAndFree` by about 33%: | ns/MB | MB/s | err% | total | benchmark |--------------------:|--------------------:|--------:|----------:|:---------- | 12,877.39 | 77,655.49 | 0.6% | 10.94 | `DataStreamInitAndFree` [1] IRC #bitcoin-core-dev 2015-11-06, 2016-11-22 Co-authored-by: David Gumberg <davidzgumberg@gmail.com>
Owner
Author
|
Weird, actual reindex-chainstate shows a serious slowdown for some reason: and |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
d2d3d65 bench/streams: replace
DataStreamdummy with lightweightSpanReaderDataStreamInitAndFreeDataStreamInitAndFreeDataStreamInitAndFreeDataStreamInitAndFreeDataStreamInitAndFreeDataStreamInitAndFreed324342 serialization: stop zeroing
DataStreambytes on destructionDataStreamInitAndFree(Unstable with ~12,963.4 iters. IncreaseminEpochIterationsto e.g. 129634)DataStreamInitAndFreeDataStreamInitAndFree(Unstable with ~14,449.4 iters. IncreaseminEpochIterationsto e.g. 144494)DataStreamInitAndFree(Unstable with ~14,854.8 iters. IncreaseminEpochIterationsto e.g. 148548)DataStreamInitAndFree(Unstable with ~15,301.9 iters. IncreaseminEpochIterationsto e.g. 153019)DataStreamInitAndFree(Unstable with ~13,136.5 iters. IncreaseminEpochIterationsto e.g. 131365)d2d3d65 bench/streams: replace
DataStreamdummy with lightweightSpanReaderDataStreamInitAndFreeDataStreamInitAndFree(Unstable with ~11,154.2 iters. IncreaseminEpochIterationsto e.g. 111542)DataStreamInitAndFreeDataStreamInitAndFree(Unstable with ~10,820.9 iters. IncreaseminEpochIterationsto e.g. 108209)DataStreamInitAndFree(Unstable with ~11,321.9 iters. IncreaseminEpochIterationsto e.g. 113219)DataStreamInitAndFreed324342 serialization: stop zeroing
DataStreambytes on destructionDataStreamInitAndFree(Unstable with ~15,176.2 iters. IncreaseminEpochIterationsto e.g. 151762)DataStreamInitAndFree(Unstable with ~15,491.1 iters. IncreaseminEpochIterationsto e.g. 154911)DataStreamInitAndFree(Unstable with ~15,204.4 iters. IncreaseminEpochIterationsto e.g. 152044)DataStreamInitAndFree(Unstable with ~16,357.8 iters. IncreaseminEpochIterationsto e.g. 163578)DataStreamInitAndFree(Unstable with ~17,445.5 iters. IncreaseminEpochIterationsto e.g. 174455)