From f49726df863dc9ac2faf8633b62876c38a000e99 Mon Sep 17 00:00:00 2001 From: Babur Makhmudov Date: Fri, 7 Feb 2025 13:00:29 +0400 Subject: [PATCH 1/2] feat: optimize allocations in writable CoW accounts This feature removes the unnecessary calls to Vec::reserve (AccountSharedData.data field) Instead when realloc is detected during deserialization of SVM buffer back into account, extra allocation happens on demand. This should decrease the pressure on allocator when transactions writing to the account do not reallocate, by avoiding allocation where possible. --- programs/bpf_loader/src/lib.rs | 5 ----- programs/bpf_loader/src/serialization.rs | 7 +++++++ sdk/transaction-context/src/lib.rs | 10 ++++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 8478abcc3ca6ee..a730e12fcf19e0 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -265,11 +265,6 @@ fn create_vm<'a, 'b>( .touch(index_in_transaction as IndexOfAccount) .map_err(|_| ())?; - if account.is_shared() { - // See BorrowedAccount::make_data_mut() as to why we reserve extra - // MAX_PERMITTED_DATA_INCREASE bytes here. - account.reserve(MAX_PERMITTED_DATA_INCREASE); - } Ok(account.data_as_mut_slice().as_mut_ptr() as u64) })), )?; diff --git a/programs/bpf_loader/src/serialization.rs b/programs/bpf_loader/src/serialization.rs index 7511a68d271a6f..614918249ac29b 100644 --- a/programs/bpf_loader/src/serialization.rs +++ b/programs/bpf_loader/src/serialization.rs @@ -579,6 +579,13 @@ fn deserialize_parameters_aligned>( borrowed_account.set_data_length(post_len)?; let allocated_bytes = post_len.saturating_sub(pre_len); if allocated_bytes > 0 { + // Just in time resize of data field within the internal AccountSharedData + // + // NOTE: this is an optimization for CoW account mappings, where the account's + // data is not immediately resized with an extra MAX_PERMITTED_DATA_INCREASE, + // but if the realloc did occur (and this branch was taken) then we need to reserve + // max permitted amount of extra space so the subsequent code will work correctly + borrowed_account.reserve(MAX_PERMITTED_DATA_INCREASE)?; borrowed_account .get_data_mut()? .get_mut(pre_len..pre_len.saturating_add(allocated_bytes)) diff --git a/sdk/transaction-context/src/lib.rs b/sdk/transaction-context/src/lib.rs index 1ba52fdaf874a7..acef38f580186d 100644 --- a/sdk/transaction-context/src/lib.rs +++ b/sdk/transaction-context/src/lib.rs @@ -974,7 +974,12 @@ impl BorrowedAccount<'_> { // memory that holds the account but it doesn't actually change content // nor length of the account. self.make_data_mut(); - self.account.reserve(additional); + // NOTE: we don't call reserve unnecessarily, as the first call to make_data_mut + // has already resized the data to an extra MAX_PERMITTED_DATA_INCREASE bytes + if additional > MAX_PERMITTED_DATA_INCREASE { + self.account + .reserve(additional - MAX_PERMITTED_DATA_INCREASE); + } Ok(()) } @@ -1004,9 +1009,6 @@ impl BorrowedAccount<'_> { // buffer with MAX_PERMITTED_DATA_INCREASE capacity so that if the // transaction reallocs, we don't have to copy the whole account data a // second time to fullfill the realloc. - // - // NOTE: The account memory region CoW code in bpf_loader::create_vm() implements the same - // logic and must be kept in sync. if self.account.is_shared() { self.account.reserve(MAX_PERMITTED_DATA_INCREASE); } From c18ac1663f85c52bf4451eda3b662033d032f825 Mon Sep 17 00:00:00 2001 From: Babur Makhmudov Date: Fri, 7 Feb 2025 17:47:40 +0400 Subject: [PATCH 2/2] fix: use saturating_sub in delta capacity calculation --- sdk/transaction-context/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/transaction-context/src/lib.rs b/sdk/transaction-context/src/lib.rs index acef38f580186d..1b1793dec5735f 100644 --- a/sdk/transaction-context/src/lib.rs +++ b/sdk/transaction-context/src/lib.rs @@ -978,7 +978,7 @@ impl BorrowedAccount<'_> { // has already resized the data to an extra MAX_PERMITTED_DATA_INCREASE bytes if additional > MAX_PERMITTED_DATA_INCREASE { self.account - .reserve(additional - MAX_PERMITTED_DATA_INCREASE); + .reserve(additional.saturating_sub(MAX_PERMITTED_DATA_INCREASE)); } Ok(())