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:
parent
e4affb9fea
commit
bad09812e3
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue