Skip to content

Commit

Permalink
Reuse VM memory across executions (#1888)
Browse files Browse the repository at this point in the history
Closes #1878. Upgrades fuel-vm dependency.

Introduces a per-thread pool for allocated VM memory instances. Whenever
a new VM instance is needed for a tx, the memory is taken from this
pool. After the tx has been executed, the memory is reset to an empty
state without deallocating, and returned to the pool.
  • Loading branch information
Dentosal committed Jun 4, 2024
1 parent a80261d commit f52102b
Show file tree
Hide file tree
Showing 33 changed files with 549 additions and 173 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Removed
- [#1913](https://github.com/FuelLabs/fuel-core/pull/1913): Removed dead code from the project.

### Changed
- [#1888](https://github.com/FuelLabs/fuel-core/pull/1888): optimization: Reuse VM memory across executions.

#### Breaking
- [#1888](https://github.com/FuelLabs/fuel-core/pull/1888): Upgraded `fuel-vm` to `0.51.0`. See [release](https://github.com/FuelLabs/fuel-vm/releases/tag/v0.51.0) for more information.

### Fixed
- [#1914](https://github.com/FuelLabs/fuel-core/pull/1914): Fixed halting of the node during synchronization in PoA service.

Expand Down
34 changes: 17 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ fuel-core-wasm-executor = { version = "0.27.0", path = "./crates/services/upgrad
fuel-core-xtask = { version = "0.0.0", path = "./xtask" }

# Fuel dependencies
fuel-vm-private = { version = "0.50.0", package = "fuel-vm", default-features = false }
fuel-vm-private = { version = "0.51.0", package = "fuel-vm", default-features = false }

# Common dependencies
anyhow = "1.0"
Expand Down
8 changes: 6 additions & 2 deletions benches/benches/block_target_gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ use fuel_core_types::{
fuel_vm::{
checked_transaction::EstimatePredicates,
consts::WORD_SIZE,
interpreter::MemoryInstance,
},
services::executor::TransactionExecutionResult,
};
Expand Down Expand Up @@ -408,8 +409,11 @@ fn run_with_service_with_extra_inputs(
}
let mut tx = tx_builder.finalize_as_transaction();
let chain_config = shared.config.snapshot_reader.chain_config().clone();
tx.estimate_predicates(&chain_config.consensus_parameters.clone().into())
.unwrap();
tx.estimate_predicates(
&chain_config.consensus_parameters.clone().into(),
MemoryInstance::new(),
)
.unwrap();
async move {
let tx_id = tx.id(&chain_config.consensus_parameters.chain_id());

Expand Down
13 changes: 8 additions & 5 deletions benches/benches/transaction_throughput.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ use fuel_core_types::{
Immediate12,
Immediate18,
},
fuel_vm::checked_transaction::{
CheckPredicateParams,
EstimatePredicates,
fuel_vm::{
checked_transaction::{
CheckPredicateParams,
EstimatePredicates,
},
interpreter::MemoryInstance,
},
};
use rand::{
Expand Down Expand Up @@ -209,7 +212,7 @@ fn predicate_transfers(c: &mut Criterion) {
.add_output(Output::coin(rng.gen(), 50, AssetId::default()))
.add_output(Output::change(rng.gen(), 0, AssetId::default()))
.finalize();
tx.estimate_predicates(&CheckPredicateParams::default())
tx.estimate_predicates(&CheckPredicateParams::default(), MemoryInstance::new())
.expect("Predicate check failed");
tx
};
Expand Down Expand Up @@ -275,7 +278,7 @@ fn predicate_transfers_eck1(c: &mut Criterion) {
.add_output(Output::coin(rng.gen(), 50, AssetId::default()))
.add_output(Output::change(rng.gen(), 0, AssetId::default()))
.finalize();
tx.estimate_predicates(&CheckPredicateParams::default())
tx.estimate_predicates(&CheckPredicateParams::default(), MemoryInstance::new())
.expect("Predicate check failed");
tx
};
Expand Down
16 changes: 13 additions & 3 deletions benches/benches/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,19 @@ where
let clock = quanta::Clock::new();

let original_db = vm.as_mut().database_mut().clone();
let original_memory = vm.memory().clone();
// During block production/validation for each state, which may affect the state of the database,
// we create a new storage transaction. The code here simulates the same behavior to have
// the same nesting level and the same performance.
let block_database_tx = original_db.clone().into_transaction();
let relayer_database_tx = block_database_tx.into_transaction();
let thread_database_tx = relayer_database_tx.into_transaction();
let tx_database_tx = thread_database_tx.into_transaction();
let tx_database_tx = relayer_database_tx.into_transaction();
let database = GenesisDatabase::new(Arc::new(tx_database_tx));
*vm.as_mut().database_mut() = database.into_transaction();

let mut total = core::time::Duration::ZERO;
for _ in 0..iters {
vm.memory_mut().clone_from(&original_memory);
let start = black_box(clock.raw());
match instruction {
Instruction::CALL(call) => {
Expand All @@ -71,6 +72,7 @@ where
vm.as_mut().database_mut().reset_changes();
}
*vm.as_mut().database_mut() = original_db;
*vm.memory_mut() = original_memory;
total
})
});
Expand All @@ -94,8 +96,16 @@ criterion_main!(benches);
// But first you need to comment `criterion_group` and `criterion_main` macros above.
//
// fn main() {
// let mut criterio = Criterion::default();
// let criterio = Criterion::default();
// let mut criterio = criterio.with_filter("vm_initialization");
// alu::run(&mut criterio);
// crypto::run(&mut criterio);
// flow::run(&mut criterio);
// mem::run(&mut criterio);
// blockchain::run(&mut criterio);
// contract_root(&mut criterio);
// state_root(&mut criterio);
// vm_initialization(&mut criterio);
// }
//
// #[test]
Expand Down
43 changes: 27 additions & 16 deletions benches/benches/vm_initialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@ use fuel_core_types::{
IntoChecked,
Ready,
},
constraints::reg_key::Reg,
constraints::reg_key::{
Reg,
RegMut,
},
consts::VM_MAX_RAM,
interpreter::NotSupportedEcal,
interpreter::{
MemoryInstance,
NotSupportedEcal,
},
Interpreter,
},
};
Expand Down Expand Up @@ -84,7 +90,7 @@ pub fn vm_initialization(c: &mut Criterion) {

// Increase the size of the script to measure the performance of the VM initialization
// with a large script. THe largest allowed script is 64 KB = 8 * 2^13 bytes.
const TX_SIZE_POWER_OF_TWO: usize = 13;
const TX_SIZE_POWER_OF_TWO: usize = 12;

for i in 5..=TX_SIZE_POWER_OF_TWO {
let size = 8 * (1 << i);
Expand All @@ -98,22 +104,29 @@ pub fn vm_initialization(c: &mut Criterion) {
let tx = tx.test_into_ready();

let name = format!("vm_initialization_with_tx_size_{}", tx_size);
let mut vm = black_box(
Interpreter::<_, _, Script, NotSupportedEcal>::with_memory_storage(),
);
group.throughput(Throughput::Bytes(tx_size as u64));
group.bench_function(name, |b| {
b.iter(|| {
unoptimized_vm_initialization_with_allocating_full_range_of_memory(&tx);
unoptimized_vm_initialization_with_allocating_full_range_of_memory(
&mut vm, &tx,
);
})
});
}

group.finish();
}

fn unoptimized_vm_initialization_with_allocating_full_range_of_memory(
#[allow(clippy::unit_arg)]
fn unoptimized_vm_initialization_with_allocating_full_range_of_memory<S>(
vm: &mut Interpreter<MemoryInstance, S, Script>,
ready_tx: &Ready<Script>,
) {
let vm = black_box(Interpreter::<_, Script, NotSupportedEcal>::with_memory_storage());

) where
S: InterpreterStorage,
{
black_box(initialize_vm_with_allocated_full_range_of_memory(
black_box(ready_tx.clone()),
vm,
Expand All @@ -122,9 +135,8 @@ fn unoptimized_vm_initialization_with_allocating_full_range_of_memory(

fn initialize_vm_with_allocated_full_range_of_memory<S>(
ready_tx: Ready<Script>,
mut vm: Interpreter<S, Script>,
) -> Interpreter<S, Script>
where
vm: &mut Interpreter<MemoryInstance, S, Script>,
) where
S: InterpreterStorage,
{
vm.init_script(ready_tx)
Expand All @@ -133,22 +145,21 @@ where
const POWER_OF_TWO_OF_HALF_VM: u64 = 25;
const VM_MEM_HALF: u64 = 1 << POWER_OF_TWO_OF_HALF_VM;
assert_eq!(VM_MEM_HALF, VM_MAX_RAM / 2);
let mut hp = VM_MAX_RAM;

for i in 0..=POWER_OF_TWO_OF_HALF_VM {
let stack = 1 << i;
let heap = VM_MAX_RAM - stack;
let heap = stack / 2;

vm.memory_mut()
.grow_stack(stack)
.expect("Should be able to grow stack");
vm.memory_mut()
.grow_heap(Reg::new(&0), heap)
.grow_heap_by(Reg::new(&0), RegMut::new(&mut hp), heap)
.expect("Should be able to grow heap");
}

vm.memory_mut()
.grow_heap(Reg::new(&0), 0)
.grow_heap_by(Reg::new(&0), RegMut::new(&mut hp), VM_MEM_HALF)
.expect("Should be able to grow heap");

vm
}
2 changes: 1 addition & 1 deletion benches/benches/vm_set/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ pub fn run(c: &mut Criterion) {
let coin_input = Input::coin_predicate(
Default::default(),
owner,
Word::MAX,
Word::MAX >> 2,
AssetId::zeroed(),
Default::default(),
Default::default(),
Expand Down
18 changes: 13 additions & 5 deletions benches/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use fuel_core_types::{
interpreter::{
diff,
InterpreterParams,
MemoryInstance,
ReceiptsCtx,
},
*,
Expand Down Expand Up @@ -107,7 +108,11 @@ pub struct VmBench {

#[derive(Debug, Clone)]
pub struct VmBenchPrepared {
pub vm: Interpreter<VmStorage<StorageTransaction<GenesisDatabase>>, Script>,
pub vm: Interpreter<
MemoryInstance,
VmStorage<StorageTransaction<GenesisDatabase>>,
Script,
>,
pub instruction: Instruction,
pub diff: diff::Diff<diff::InitialVmState>,
}
Expand Down Expand Up @@ -454,16 +459,19 @@ impl TryFrom<VmBench> for VmBenchPrepared {
.maturity(maturity)
.with_params(params.clone())
.finalize();
tx.estimate_predicates(&CheckPredicateParams::from(&params))
.unwrap();
tx.estimate_predicates(
&CheckPredicateParams::from(&params),
MemoryInstance::new(),
)
.unwrap();
let tx = tx.into_checked(height, &params).unwrap();
let interpreter_params = InterpreterParams::new(gas_price, &params);

let mut txtor = Transactor::new(db, interpreter_params);
let mut txtor = Transactor::new(MemoryInstance::new(), db, interpreter_params);

txtor.transact(tx);

let mut vm: Interpreter<_, _> = txtor.into();
let mut vm: Interpreter<_, _, _> = txtor.into();

if let Some(receipts_ctx) = receipts_ctx {
*vm.receipts_mut() = receipts_ctx;
Expand Down
Loading

0 comments on commit f52102b

Please sign in to comment.