AccountSharedData: optimize set_data_from_slice

Remove memcmp and try to avoid allocations by trying to reuse or extend
existing capacity.
This commit is contained in:
Alessandro Decina 2022-09-16 11:25:36 +00:00
parent e4affb9fea
commit bad09812e3
2 changed files with 193 additions and 8 deletions

152
sdk/benches/accounts.rs Normal file
View File

@ -0,0 +1,152 @@
#![feature(test)]
#![allow(clippy::integer_arithmetic)]
use solana_sdk::{entrypoint::MAX_PERMITTED_DATA_INCREASE, pubkey::Pubkey};
extern crate test;
use {solana_sdk::account::AccountSharedData, test::Bencher};
fn bench_unchanged(bencher: &mut Bencher, size: usize) {
let mut account = AccountSharedData::new(42, 0, &Pubkey::new_unique());
let new_data = vec![42; size];
account.set_data(new_data.clone());
bencher.iter(|| {
account.set_data_from_slice(&new_data);
});
}
fn bench_changed(bencher: &mut Bencher, size: usize) {
let mut account = AccountSharedData::new(42, 0, &Pubkey::new_unique());
let initial_data = vec![42; size];
account.set_data(initial_data);
let new_data = (0..10)
.map(|i| {
let mut data = vec![42; size];
data[size / 10 * i] = 43;
data
})
.collect::<Vec<_>>();
let mut new_data = new_data.iter().cycle();
bencher.iter(|| {
account.set_data_from_slice(new_data.next().unwrap());
});
}
fn bench_grow(bencher: &mut Bencher, size: usize) {
let mut account = AccountSharedData::new(42, 0, &Pubkey::new_unique());
let initial_data = vec![42; size];
account.set_data(initial_data);
let new_data = (0..10)
.map(|i| {
let mut data = vec![42; size];
data.resize(size + (i * MAX_PERMITTED_DATA_INCREASE), 42);
data
})
.collect::<Vec<_>>();
let mut new_data = new_data.iter().cycle();
bencher.iter(|| {
account.set_data_from_slice(new_data.next().unwrap());
});
}
fn bench_shrink(bencher: &mut Bencher, size: usize) {
let mut account = AccountSharedData::new(42, 0, &Pubkey::new_unique());
let initial_data = vec![42; size];
account.set_data(initial_data);
let new_data = (0..10)
.map(|i| {
let mut data = vec![42; size];
data.resize(size + (i * MAX_PERMITTED_DATA_INCREASE), 42);
data
})
.collect::<Vec<_>>();
let mut new_data = new_data.iter().rev().cycle();
bencher.iter(|| {
account.set_data_from_slice(new_data.next().unwrap());
});
}
#[bench]
fn bench_set_data_from_slice_unchanged_1k(b: &mut Bencher) {
bench_unchanged(b, 1024)
}
#[bench]
fn bench_set_data_from_slice_unchanged_100k(b: &mut Bencher) {
bench_unchanged(b, 1024 * 100)
}
#[bench]
fn bench_set_data_from_slice_unchanged_1mb(b: &mut Bencher) {
bench_unchanged(b, 1024 * 1024)
}
#[bench]
fn bench_set_data_from_slice_unchanged_10mb(b: &mut Bencher) {
bench_unchanged(b, 1024 * 1024 * 10)
}
#[bench]
fn bench_set_data_from_slice_changed_1k(b: &mut Bencher) {
bench_changed(b, 1024)
}
#[bench]
fn bench_set_data_from_slice_changed_100k(b: &mut Bencher) {
bench_changed(b, 1024 * 100)
}
#[bench]
fn bench_set_data_from_slice_changed_1mb(b: &mut Bencher) {
bench_changed(b, 1024 * 1024)
}
#[bench]
fn bench_set_data_from_slice_changed_10mb(b: &mut Bencher) {
bench_changed(b, 1024 * 1024 * 10)
}
#[bench]
fn bench_set_data_from_slice_grow_1k(b: &mut Bencher) {
bench_grow(b, 1024)
}
#[bench]
fn bench_set_data_from_slice_grow_100k(b: &mut Bencher) {
bench_grow(b, 1024 * 100)
}
#[bench]
fn bench_set_data_from_slice_grow_1mb(b: &mut Bencher) {
bench_grow(b, 1024 * 1024)
}
#[bench]
fn bench_set_data_from_slice_grow_10mb(b: &mut Bencher) {
bench_grow(b, 1024 * 1024 * 10)
}
#[bench]
fn bench_set_data_from_slice_shrink_100k(b: &mut Bencher) {
bench_shrink(b, 1024 * 100)
}
#[bench]
fn bench_set_data_from_slice_shrink_1mb(b: &mut Bencher) {
bench_shrink(b, 1024 * 1024)
}
#[bench]
fn bench_set_data_from_slice_shrink_10mb(b: &mut Bencher) {
bench_shrink(b, 1024 * 1024 * 10)
}

View File

@ -11,7 +11,7 @@ use {
solana_program::{account_info::AccountInfo, debug_account_data::*, sysvar::Sysvar},
std::{
cell::{Ref, RefCell},
fmt,
fmt, ptr,
rc::Rc,
sync::Arc,
},
@ -538,17 +538,50 @@ impl Account {
}
impl AccountSharedData {
pub fn set_data_from_slice(&mut self, data: &[u8]) {
let len = self.data.len();
let len_different = len != data.len();
let different = len_different || data != &self.data[..];
if different {
self.data = Arc::new(data.to_vec());
}
pub fn set_data_from_slice(&mut self, new_data: &[u8]) {
let data = match Arc::get_mut(&mut self.data) {
// The buffer isn't shared, so we're going to memcpy in place.
Some(data) => data,
// If the buffer is shared, the cheapest thing to do is to clone the
// incoming slice and replace the buffer.
None => return self.set_data(new_data.to_vec()),
};
let new_len = new_data.len();
// Reserve additional capacity if needed. Here we make the assumption
// that growing the current buffer is cheaper than doing a whole new
// allocation to make `new_data` owned.
//
// This assumption holds true during CPI, especially when the account
// size doesn't change but the account is only changed in place. And
// it's also true when the account is grown by a small margin (the
// realloc limit is quite low), in which case the allocator can just
// update the allocation metadata without moving.
//
// Shrinking and copying in place is always faster than making
// `new_data` owned, since shrinking boils down to updating the Vec's
// length.
data.reserve(new_len.saturating_sub(data.len()));
// Safety:
// We just reserved enough capacity. We set data::len to 0 to avoid
// possible UB on panic (dropping uninitialized elements), do the copy,
// finally set the new length once everything is initialized.
#[allow(clippy::uninit_vec)]
// this is a false positive, the lint doesn't currently special case set_len(0)
unsafe {
data.set_len(0);
ptr::copy_nonoverlapping(new_data.as_ptr(), data.as_mut_ptr(), new_len);
data.set_len(new_len);
};
}
pub fn set_data(&mut self, data: Vec<u8>) {
self.data = Arc::new(data);
}
pub fn new(lamports: u64, space: usize, owner: &Pubkey) -> Self {
shared_new(lamports, space, owner)
}