diff --git a/sdk/benches/accounts.rs b/sdk/benches/accounts.rs new file mode 100644 index 000000000..2caf0e421 --- /dev/null +++ b/sdk/benches/accounts.rs @@ -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::>(); + + 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::>(); + + 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::>(); + + 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) +} diff --git a/sdk/src/account.rs b/sdk/src/account.rs index 1f5305889..e2057d3c5 100644 --- a/sdk/src/account.rs +++ b/sdk/src/account.rs @@ -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) { self.data = Arc::new(data); } + pub fn new(lamports: u64, space: usize, owner: &Pubkey) -> Self { shared_new(lamports, space, owner) }