From febfa30e0b0c0a0a74dcb287b18fec24c0c4f8af Mon Sep 17 00:00:00 2001 From: aldenhu Date: Tue, 12 Dec 2023 03:51:32 +0000 Subject: [PATCH] Io Gas adjustments 1. read bytes charged in 4k steps 2. remove per write op free quota, while make per byte write cheaper tune write gas numbers --- .../src/gas_schedule/transaction.rs | 32 +++++++---- .../aptos-vm-types/src/storage/io_pricing.rs | 54 +++++++++++++++---- aptos-move/aptos-vm-types/src/storage/mod.rs | 2 +- .../src/storage/space_pricing.rs | 2 +- .../src/tests/per_category_gas_limits.rs | 8 +-- .../src/tests/failed_transaction_tests.rs | 2 +- aptos-move/framework/table-natives/src/lib.rs | 12 ++++- 7 files changed, 85 insertions(+), 27 deletions(-) diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs index f2be95b29699b..fd8f088dbf1f6 100644 --- a/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs +++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs @@ -76,38 +76,50 @@ crate::gas_schedule::macros::define_gas_parameters!( [ storage_io_per_state_slot_read: InternalGasPerArg, { 0..=9 => "load_data.base", 10.. => "storage_io_per_state_slot_read"}, - 300_000, + // At the current mainnet scale, we should assume most levels of the (hexary) JMT nodes + // in cache, hence target charging 1-2 4k-sized pages for each read. Notice the cost + // of seeking for the leaf node is covered by the first page of the "value size fee" + // (storage_io_per_state_byte_read) defined below. + 800_000, ], [ storage_io_per_state_byte_read: InternalGasPerByte, { 0..=9 => "load_data.per_byte", 10.. => "storage_io_per_state_byte_read"}, - 300, + // Notice in the latest IoPricing, bytes are charged at 4k intervals (even the smallest + // read will be charged for 4KB) to reflect the assumption that every roughly 4k bytes + // might require a separate random IO upon the FS. + 100, ], [load_data_failure: InternalGas, "load_data.failure", 0], // Gas parameters for writing data to storage. [ storage_io_per_state_slot_write: InternalGasPerArg, { 0..=9 => "write_data.per_op", 10.. => "storage_io_per_state_slot_write"}, - 300_000, + // The cost of writing down the upper level new JMT nodes are shared between transactions + // because we write down the JMT in batches, however the bottom levels will be specific + // to each transactions assuming they don't touch exactly the same leaves. It's fair to + // target roughly 1-2 full internal JMT nodes (about 1KB in total) worth of writes for + // each write op. + 100_000, ], [ - write_data_per_new_item: InternalGasPerArg, - "write_data.new_item", - 1_280_000 + legacy_write_data_per_new_item: InternalGasPerArg, + {0..=9 => "write_data.new_item"}, + 1_280_000, ], [ storage_io_per_state_byte_write: InternalGasPerByte, { 0..=9 => "write_data.per_byte_in_key", 10.. => "storage_io_per_state_byte_write"}, - 5_000 + 100, ], [ - write_data_per_byte_in_val: InternalGasPerByte, - "write_data.per_byte_in_val", + legacy_write_data_per_byte_in_val: InternalGasPerByte, + { 0..=9 => "write_data.per_byte_in_val" }, 10_000 ], [memory_quota: AbstractValueSize, { 1.. => "memory_quota" }, 10_000_000], [ - free_write_bytes_quota: NumBytes, + legacy_free_write_bytes_quota: NumBytes, { 5.. => "free_write_bytes_quota" }, 1024, // 1KB free per state write ], diff --git a/aptos-move/aptos-vm-types/src/storage/io_pricing.rs b/aptos-move/aptos-vm-types/src/storage/io_pricing.rs index 18f8b0a6bcaae..82de5e4ec7939 100644 --- a/aptos-move/aptos-vm-types/src/storage/io_pricing.rs +++ b/aptos-move/aptos-vm-types/src/storage/io_pricing.rs @@ -34,9 +34,9 @@ impl IoPricingV1 { fn new(gas_params: &AptosGasParameters) -> Self { Self { write_data_per_op: gas_params.vm.txn.storage_io_per_state_slot_write, - write_data_per_new_item: gas_params.vm.txn.write_data_per_new_item, + write_data_per_new_item: gas_params.vm.txn.legacy_write_data_per_new_item, write_data_per_byte_in_key: gas_params.vm.txn.storage_io_per_state_byte_write, - write_data_per_byte_in_val: gas_params.vm.txn.write_data_per_byte_in_val, + write_data_per_byte_in_val: gas_params.vm.txn.legacy_write_data_per_byte_in_val, load_data_base: gas_params.vm.txn.storage_io_per_state_slot_read * NumArgs::new(1), load_data_per_byte: gas_params.vm.txn.storage_io_per_state_byte_read, load_data_failure: gas_params.vm.txn.load_data_failure, @@ -120,7 +120,7 @@ impl IoPricingV2 { 0 => unreachable!("PricingV2 not applicable for feature version 0"), 1..=2 => 0.into(), 3..=4 => 1024.into(), - 5.. => gas_params.vm.txn.free_write_bytes_quota, + 5.. => gas_params.vm.txn.legacy_free_write_bytes_quota, } } @@ -167,7 +167,7 @@ impl IoPricingV2 { #[derive(Debug, Clone)] pub struct IoPricingV3 { pub feature_version: u64, - pub free_write_bytes_quota: NumBytes, + pub legacy_free_write_bytes_quota: NumBytes, } impl IoPricingV3 { @@ -183,7 +183,7 @@ impl IoPricingV3 { let value_size = NumBytes::new(value_size); (key_size + value_size) - .checked_sub(self.free_write_bytes_quota) + .checked_sub(self.legacy_free_write_bytes_quota) .unwrap_or(NumBytes::zero()) } @@ -204,11 +204,44 @@ impl IoPricingV3 { } } +#[derive(Debug, Clone)] +pub struct IoPricingV4; + +impl IoPricingV4 { + fn calculate_read_gas( + &self, + loaded: NumBytes, + ) -> impl GasExpression { + // round up bytes to whole pages + const PAGE_SIZE: u64 = 4096; + + let loaded_u64: u64 = loaded.into(); + let r = loaded_u64 % PAGE_SIZE; + let rounded_up = loaded_u64 + if r == 0 { 0 } else { PAGE_SIZE - r }; + + STORAGE_IO_PER_STATE_SLOT_READ * NumArgs::from(1) + + STORAGE_IO_PER_STATE_BYTE_READ * NumBytes::new(rounded_up) + } + + fn io_gas_per_write( + &self, + key: &StateKey, + op_size: &WriteOpSize, + ) -> impl GasExpression { + let key_size = NumBytes::new(key.size() as u64); + let value_size = NumBytes::new(op_size.write_len().unwrap_or(0)); + let size = key_size + value_size; + + STORAGE_IO_PER_STATE_SLOT_WRITE * NumArgs::new(1) + STORAGE_IO_PER_STATE_BYTE_WRITE * size + } +} + #[derive(Clone, Debug)] pub enum IoPricing { V1(IoPricingV1), V2(IoPricingV2), V3(IoPricingV3), + V4(IoPricingV4), } impl IoPricing { @@ -230,10 +263,11 @@ impl IoPricing { gas_params, )), }, - 10.. => V3(IoPricingV3 { + 10..=11 => V3(IoPricingV3 { feature_version, - free_write_bytes_quota: gas_params.vm.txn.free_write_bytes_quota, + legacy_free_write_bytes_quota: gas_params.vm.txn.legacy_free_write_bytes_quota, }), + 12.. => V4(IoPricingV4), } } @@ -253,7 +287,8 @@ impl IoPricing { }, )), V2(v2) => Either::Left(v2.calculate_read_gas(bytes_loaded)), - V3(v3) => Either::Right(v3.calculate_read_gas(bytes_loaded)), + V3(v3) => Either::Right(Either::Left(v3.calculate_read_gas(bytes_loaded))), + V4(v4) => Either::Right(Either::Right(v4.calculate_read_gas(bytes_loaded))), } } @@ -269,7 +304,8 @@ impl IoPricing { match self { V1(v1) => Either::Left(v1.io_gas_per_write(key, op_size)), V2(v2) => Either::Left(v2.io_gas_per_write(key, op_size)), - V3(v3) => Either::Right(v3.io_gas_per_write(key, op_size)), + V3(v3) => Either::Right(Either::Left(v3.io_gas_per_write(key, op_size))), + V4(v4) => Either::Right(Either::Right(v4.io_gas_per_write(key, op_size))), } } } diff --git a/aptos-move/aptos-vm-types/src/storage/mod.rs b/aptos-move/aptos-vm-types/src/storage/mod.rs index 9812d411d6b6b..d70336f052be4 100644 --- a/aptos-move/aptos-vm-types/src/storage/mod.rs +++ b/aptos-move/aptos-vm-types/src/storage/mod.rs @@ -43,7 +43,7 @@ impl StorageGasParameters { Self { io_pricing: IoPricing::V3(IoPricingV3 { feature_version: LATEST_GAS_FEATURE_VERSION, - free_write_bytes_quota, + legacy_free_write_bytes_quota: free_write_bytes_quota, }), space_pricing: DiskSpacePricing::v1(), change_set_configs: ChangeSetConfigs::unlimited_at_gas_feature_version( diff --git a/aptos-move/aptos-vm-types/src/storage/space_pricing.rs b/aptos-move/aptos-vm-types/src/storage/space_pricing.rs index df9d8d0a2a770..02e305a4102ec 100644 --- a/aptos-move/aptos-vm-types/src/storage/space_pricing.rs +++ b/aptos-move/aptos-vm-types/src/storage/space_pricing.rs @@ -89,7 +89,7 @@ impl DiskSpacePricing { value_size: u64, ) -> NumBytes { let size = NumBytes::new(key.size() as u64) + NumBytes::new(value_size); - size.checked_sub(params.free_write_bytes_quota) + size.checked_sub(params.legacy_free_write_bytes_quota) .unwrap_or(NumBytes::zero()) } diff --git a/aptos-move/e2e-move-tests/src/tests/per_category_gas_limits.rs b/aptos-move/e2e-move-tests/src/tests/per_category_gas_limits.rs index 012538886f613..56a5269bb0542 100644 --- a/aptos-move/e2e-move-tests/src/tests/per_category_gas_limits.rs +++ b/aptos-move/e2e-move-tests/src/tests/per_category_gas_limits.rs @@ -91,7 +91,7 @@ fn io_limit_reached_by_new_bytes() { // Allow 10 value bytes charged at most. gas_params.vm.txn.max_io_gas = 110_000_000.into(); // Make the key bytes free, only play around value sizes. - gas_params.vm.txn.free_write_bytes_quota = state_key_size(); + gas_params.vm.txn.legacy_free_write_bytes_quota = state_key_size(); }); test_create_multiple_items(&mut h, &acc, |status| { @@ -112,7 +112,7 @@ fn storage_limit_reached_by_new_bytes() { // Allow 10 value bytes charged at most. gas_params.vm.txn.max_storage_fee = 11_000_000.into(); // Make the key bytes free, only play around value sizes. - gas_params.vm.txn.free_write_bytes_quota = state_key_size(); + gas_params.vm.txn.legacy_free_write_bytes_quota = state_key_size(); }); test_create_multiple_items(&mut h, &acc, |status| { @@ -134,7 +134,7 @@ fn out_of_gas_while_charging_write_gas() { // Bump max gas allowed gas_params.vm.txn.maximum_number_of_gas_units = 1_000_000_000.into(); // Make the key bytes free, only play around value sizes. - gas_params.vm.txn.free_write_bytes_quota = state_key_size(); + gas_params.vm.txn.legacy_free_write_bytes_quota = state_key_size(); }); // Allow 10 value bytes charged at most. Notice this is in external units. h.set_max_gas_per_txn(110_000); @@ -156,7 +156,7 @@ fn out_of_gas_while_charging_storage_fee() { // Bump max gas allowed gas_params.vm.txn.maximum_number_of_gas_units = 1_000_000_000.into(); // Make the key bytes free, only play around value sizes. - gas_params.vm.txn.free_write_bytes_quota = state_key_size(); + gas_params.vm.txn.legacy_free_write_bytes_quota = state_key_size(); }); // Allow 10 value bytes charged at most. Notice this is in external units, // which is 1/100x octas or 1Mx internal units. diff --git a/aptos-move/e2e-testsuite/src/tests/failed_transaction_tests.rs b/aptos-move/e2e-testsuite/src/tests/failed_transaction_tests.rs index 30716ef97fbfd..034bc2d68204e 100644 --- a/aptos-move/e2e-testsuite/src/tests/failed_transaction_tests.rs +++ b/aptos-move/e2e-testsuite/src/tests/failed_transaction_tests.rs @@ -40,7 +40,7 @@ fn failed_transaction_cleanup_test() { let gas_params = AptosGasParameters::zeros(); let storage_gas_params = - StorageGasParameters::unlimited(gas_params.vm.txn.free_write_bytes_quota); + StorageGasParameters::unlimited(gas_params.vm.txn.legacy_free_write_bytes_quota); let change_set_configs = storage_gas_params.change_set_configs.clone(); diff --git a/aptos-move/framework/table-natives/src/lib.rs b/aptos-move/framework/table-natives/src/lib.rs index 55717e75c0d63..331694c6b7562 100644 --- a/aptos-move/framework/table-natives/src/lib.rs +++ b/aptos-move/framework/table-natives/src/lib.rs @@ -287,7 +287,17 @@ fn charge_load_cost( match loaded { Some(Some(num_bytes)) => { - context.charge(COMMON_LOAD_BASE_NEW + COMMON_LOAD_PER_BYTE * num_bytes) + if context.gas_feature_version() >= 12 { + // round up bytes to whole pages + const PAGE_SIZE: u64 = 4096; + let loaded_u64: u64 = num_bytes.into(); + let r = loaded_u64 % PAGE_SIZE; + let rounded_up = loaded_u64 + if r == 0 { 0 } else { PAGE_SIZE - r }; + context + .charge(COMMON_LOAD_BASE_NEW + COMMON_LOAD_PER_BYTE * NumBytes::new(rounded_up)) + } else { + context.charge(COMMON_LOAD_BASE_NEW + COMMON_LOAD_PER_BYTE * num_bytes) + } }, Some(None) => context.charge(COMMON_LOAD_BASE_NEW + COMMON_LOAD_FAILURE), None => Ok(()),