Use SmallVec in Bitfield (#3025)

## Issue Addressed

Alternative to #2935

## Proposed Changes

Replace the `Vec<u8>` inside `Bitfield` with a `SmallVec<[u8; 32>`. This eliminates heap allocations for attestation bitfields until we reach 500K validators, at which point we can consider increasing `SMALLVEC_LEN` to 40 or 48.

While running Lighthouse under `heaptrack` I found that SSZ encoding and decoding of bitfields corresponded to 22% of all allocations by count. I've confirmed that with this change applied those allocations disappear entirely.

## Additional Info

We can win another 8 bytes of space by using `smallvec`'s [`union` feature](https://docs.rs/smallvec/1.8.0/smallvec/#union), although I might leave that for a future PR because I don't know how experimental that feature is and whether it uses some spicy `unsafe` blocks.
This commit is contained in:
Michael Sproul
2022-02-17 23:55:04 +00:00
parent 0a6a8ea3b0
commit da4ca024f1
6 changed files with 174 additions and 110 deletions

View File

@@ -45,6 +45,7 @@ parking_lot = "0.11.1"
itertools = "0.10.0"
superstruct = "0.4.0"
serde_json = "1.0.74"
smallvec = "1.8.0"
[dev-dependencies]
criterion = "0.3.3"

View File

@@ -110,9 +110,34 @@ impl<T: EthSpec> SlotData for Attestation<T> {
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
use super::*;
// Check the in-memory size of an `Attestation`, which is useful for reasoning about memory
// and preventing regressions.
//
// This test will only pass with `blst`, if we run these tests with Milagro or another
// BLS library in future we will have to make it generic.
#[test]
fn size_of() {
use std::mem::size_of;
let aggregation_bits =
size_of::<BitList<<MainnetEthSpec as EthSpec>::MaxValidatorsPerCommittee>>();
let attestation_data = size_of::<AttestationData>();
let signature = size_of::<AggregateSignature>();
assert_eq!(aggregation_bits, 56);
assert_eq!(attestation_data, 128);
assert_eq!(signature, 288 + 16);
let attestation_expected = aggregation_bits + attestation_data + signature;
assert_eq!(attestation_expected, 488);
assert_eq!(
size_of::<Attestation<MainnetEthSpec>>(),
attestation_expected
);
}
ssz_and_tree_hash_tests!(Attestation<MainnetEthSpec>);
}

View File

@@ -1,9 +1,10 @@
use super::*;
use crate::{BitList, BitVector, Unsigned};
use smallvec::smallvec;
impl<N: Unsigned + Clone> TestRandom for BitList<N> {
fn random_for_test(rng: &mut impl RngCore) -> Self {
let mut raw_bytes = vec![0; std::cmp::max(1, (N::to_usize() + 7) / 8)];
let mut raw_bytes = smallvec![0; std::cmp::max(1, (N::to_usize() + 7) / 8)];
rng.fill_bytes(&mut raw_bytes);
Self::from_bytes(raw_bytes).expect("we generate a valid BitList")
}
@@ -11,7 +12,7 @@ impl<N: Unsigned + Clone> TestRandom for BitList<N> {
impl<N: Unsigned + Clone> TestRandom for BitVector<N> {
fn random_for_test(rng: &mut impl RngCore) -> Self {
let mut raw_bytes = vec![0; std::cmp::max(1, (N::to_usize() + 7) / 8)];
let mut raw_bytes = smallvec![0; std::cmp::max(1, (N::to_usize() + 7) / 8)];
rng.fill_bytes(&mut raw_bytes);
Self::from_bytes(raw_bytes).expect("we generate a valid BitVector")
}