Skip to content

Commit

Permalink
Merge pull request #703 from CosmWasm/memory-cache-avoid-serialization
Browse files Browse the repository at this point in the history
Avoid InMemoryCache store/load serialization/deserialization
  • Loading branch information
webmaster128 authored Jan 12, 2021
2 parents f548fd4 + 94b4308 commit e69ac7e
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 44 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
- Remove `from_address` from `BankMsg::Send`, as it always sends from the
contract address, and this is consistent with other `CosmosMsg` variants.

**cosmwasm-vm**

- Avoid serialization of Modules in `InMemoryCache`, for performance. (#697)

Also, remove `memory_limit` from `InstanceOptions`, and define it instead at
`Cache` level (same memory limit for all cached instances).

## 0.13.1 (2020-01-12)

**cosmwasm-std**
Expand Down
3 changes: 2 additions & 1 deletion contracts/reflect/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ fn dispatch_custom_query() {
// stub gives us defaults. Consume it and override...
let custom = mock_dependencies_with_custom_querier(&[]);
// we cannot use mock_instance, so we just copy and modify code from cosmwasm_vm::testing
let mut deps = Instance::from_code(WASM, custom, mock_instance_options()).unwrap();
let (instance_options, memory_limit) = mock_instance_options();
let mut deps = Instance::from_code(WASM, custom, instance_options, memory_limit).unwrap();

// we don't even initialize, just trigger a query
let res = query(
Expand Down
6 changes: 4 additions & 2 deletions contracts/staking/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ fn initialization_with_missing_validator() {
backend
.querier
.update_staking("ustake", &[sample_validator("john")], &[]);
let mut deps = Instance::from_code(WASM, backend, mock_instance_options()).unwrap();
let (instance_options, memory_limit) = mock_instance_options();
let mut deps = Instance::from_code(WASM, backend, instance_options, memory_limit).unwrap();

let creator = HumanAddr::from("creator");
let msg = InitMsg {
Expand Down Expand Up @@ -82,7 +83,8 @@ fn proper_initialization() {
],
&[],
);
let mut deps = Instance::from_code(WASM, backend, mock_instance_options()).unwrap();
let (instance_options, memory_limit) = mock_instance_options();
let mut deps = Instance::from_code(WASM, backend, instance_options, memory_limit).unwrap();
assert_eq!(deps.required_features.len(), 1);
assert!(deps.required_features.contains("staking"));

Expand Down
12 changes: 8 additions & 4 deletions packages/vm/benches/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const DEFAULT_GAS_LIMIT: u64 = 400_000;
const DEFAULT_INSTANCE_OPTIONS: InstanceOptions = InstanceOptions {
gas_limit: DEFAULT_GAS_LIMIT,
print_debug: false,
memory_limit: DEFAULT_MEMORY_LIMIT,
};
// Cache
const MEMORY_CACHE_SIZE: Size = Size::mebi(200);
Expand All @@ -30,8 +29,9 @@ fn bench_instance(c: &mut Criterion) {
group.bench_function("compile and instantiate", |b| {
b.iter(|| {
let backend = mock_backend(&[]);
let (instance_options, memory_limit) = mock_instance_options();
let _instance =
Instance::from_code(CONTRACT, backend, mock_instance_options()).unwrap();
Instance::from_code(CONTRACT, backend, instance_options, memory_limit).unwrap();
});
});

Expand All @@ -41,7 +41,8 @@ fn bench_instance(c: &mut Criterion) {
gas_limit: 500_000_000_000,
..DEFAULT_INSTANCE_OPTIONS
};
let mut instance = Instance::from_code(CONTRACT, backend, much_gas).unwrap();
let mut instance =
Instance::from_code(CONTRACT, backend, much_gas, DEFAULT_MEMORY_LIMIT).unwrap();

b.iter(|| {
let info = mock_info("creator", &coins(1000, "earth"));
Expand All @@ -58,7 +59,8 @@ fn bench_instance(c: &mut Criterion) {
gas_limit: 500_000_000_000,
..DEFAULT_INSTANCE_OPTIONS
};
let mut instance = Instance::from_code(CONTRACT, backend, much_gas).unwrap();
let mut instance =
Instance::from_code(CONTRACT, backend, much_gas, DEFAULT_MEMORY_LIMIT).unwrap();

let info = mock_info("creator", &coins(1000, "earth"));
let msg = br#"{"verifier": "verifies", "beneficiary": "benefits"}"#;
Expand All @@ -85,6 +87,7 @@ fn bench_cache(c: &mut Criterion) {
base_dir: TempDir::new().unwrap().into_path(),
supported_features: features_from_csv("staking"),
memory_cache_size: MEMORY_CACHE_SIZE,
instance_memory_limit: DEFAULT_MEMORY_LIMIT,
};

group.bench_function("save wasm", |b| {
Expand All @@ -102,6 +105,7 @@ fn bench_cache(c: &mut Criterion) {
base_dir: TempDir::new().unwrap().into_path(),
supported_features: features_from_csv("staking"),
memory_cache_size: Size(0),
instance_memory_limit: DEFAULT_MEMORY_LIMIT,
};
let mut cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(non_memcache).unwrap() };
Expand Down
23 changes: 16 additions & 7 deletions packages/vm/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,17 @@ pub struct CacheOptions {
pub base_dir: PathBuf,
pub supported_features: HashSet<String>,
pub memory_cache_size: Size,
/// Memory limit for instances, in bytes. Use a value that is divisible by the Wasm page size 65536,
/// e.g. full MiBs.
pub instance_memory_limit: Size,
}

pub struct Cache<A: Api, S: Storage, Q: Querier> {
wasm_path: PathBuf,
supported_features: HashSet<String>,
/// Instances memory limit in bytes. Use a value that is divisible by the Wasm page size 65536,
/// e.g. full MiBs.
instance_memory_limit: Size,
memory_cache: InMemoryCache,
fs_cache: FileSystemCache,
stats: Stats,
Expand All @@ -56,12 +62,13 @@ where
///
/// This function is marked unsafe due to `FileSystemCache::new`, which implicitly
/// assumes the disk contents are correct, and there's no way to ensure the artifacts
// stored in the cache haven't been corrupted or tampered with.
/// stored in the cache haven't been corrupted or tampered with.
pub unsafe fn new(options: CacheOptions) -> VmResult<Self> {
let CacheOptions {
base_dir,
supported_features,
memory_cache_size,
instance_memory_limit,
} = options;
let wasm_path = base_dir.join(WASM_DIR);
create_dir_all(&wasm_path)
Expand All @@ -72,6 +79,7 @@ where
Ok(Cache {
wasm_path,
supported_features,
instance_memory_limit,
memory_cache: InMemoryCache::new(memory_cache_size),
fs_cache,
stats: Stats::default(),
Expand Down Expand Up @@ -116,16 +124,16 @@ where
backend: Backend<A, S, Q>,
options: InstanceOptions,
) -> VmResult<Instance<A, S, Q>> {
let store = make_runtime_store(options.memory_limit);
// Get module from memory cache
if let Some(module) = self.memory_cache.load(checksum, &store)? {
if let Some(module) = self.memory_cache.load(checksum)? {
self.stats.hits_memory_cache += 1;
let instance =
Instance::from_module(&module, backend, options.gas_limit, options.print_debug)?;
return Ok(instance);
}

// Get module from file system cache
let store = make_runtime_store(self.instance_memory_limit);
if let Some(module) = self.fs_cache.load(checksum, &store)? {
self.stats.hits_fs_cache += 1;
let instance =
Expand All @@ -141,7 +149,7 @@ where
// stored the old module format.
let wasm = self.load_wasm(checksum)?;
self.stats.misses += 1;
let module = compile_and_use(&wasm, options.memory_limit)?;
let module = compile_and_use(&wasm, self.instance_memory_limit)?;
let instance =
Instance::from_module(&module, backend, options.gas_limit, options.print_debug)?;
self.fs_cache.store(checksum, &module)?;
Expand Down Expand Up @@ -201,7 +209,6 @@ mod tests {
const TESTING_MEMORY_LIMIT: Size = Size::mebi(16);
const TESTING_OPTIONS: InstanceOptions = InstanceOptions {
gas_limit: TESTING_GAS_LIMIT,
memory_limit: TESTING_MEMORY_LIMIT,
print_debug: false,
};
const TESTING_MEMORY_CACHE_SIZE: Size = Size::mebi(200);
Expand All @@ -217,6 +224,7 @@ mod tests {
base_dir: TempDir::new().unwrap().into_path(),
supported_features: default_features(),
memory_cache_size: TESTING_MEMORY_CACHE_SIZE,
instance_memory_limit: TESTING_MEMORY_LIMIT,
}
}

Expand Down Expand Up @@ -298,6 +306,7 @@ mod tests {
base_dir: tmp_dir.path().to_path_buf(),
supported_features: default_features(),
memory_cache_size: TESTING_MEMORY_CACHE_SIZE,
instance_memory_limit: TESTING_MEMORY_LIMIT,
};
let mut cache1: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(options1).unwrap() };
Expand All @@ -309,6 +318,7 @@ mod tests {
base_dir: tmp_dir.path().to_path_buf(),
supported_features: default_features(),
memory_cache_size: TESTING_MEMORY_CACHE_SIZE,
instance_memory_limit: TESTING_MEMORY_LIMIT,
};
let cache2: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(options2).unwrap() };
Expand Down Expand Up @@ -342,6 +352,7 @@ mod tests {
base_dir: tmp_dir.path().to_path_buf(),
supported_features: default_features(),
memory_cache_size: TESTING_MEMORY_CACHE_SIZE,
instance_memory_limit: TESTING_MEMORY_LIMIT,
};
let mut cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(options).unwrap() };
Expand Down Expand Up @@ -582,7 +593,6 @@ mod tests {
// Init from module cache
let options = InstanceOptions {
gas_limit: 10,
memory_limit: TESTING_MEMORY_LIMIT,
print_debug: false,
};
let mut instance1 = cache.get_instance(&id, backend1, options).unwrap();
Expand All @@ -601,7 +611,6 @@ mod tests {
// Init from memory cache
let options = InstanceOptions {
gas_limit: TESTING_GAS_LIMIT,
memory_limit: TESTING_MEMORY_LIMIT,
print_debug: false,
};
let mut instance2 = cache.get_instance(&id, backend2, options).unwrap();
Expand Down
12 changes: 7 additions & 5 deletions packages/vm/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ pub struct GasReport {
#[derive(Copy, Clone, Debug)]
pub struct InstanceOptions {
pub gas_limit: u64,
/// Memory limit in bytes. Use a value that is divisible by the Wasm page size 65536, e.g. full MiBs.
pub memory_limit: Size,
pub print_debug: bool,
}

Expand All @@ -60,8 +58,9 @@ where
code: &[u8],
backend: Backend<A, S, Q>,
options: InstanceOptions,
memory_limit: Size,
) -> VmResult<Self> {
let module = compile_and_use(code, options.memory_limit)?;
let module = compile_and_use(code, memory_limit)?;
Instance::from_module(&module, backend, options.gas_limit, options.print_debug)
}

Expand Down Expand Up @@ -313,7 +312,9 @@ mod tests {
#[test]
fn required_features_works() {
let backend = mock_backend(&[]);
let instance = Instance::from_code(CONTRACT, backend, mock_instance_options()).unwrap();
let (instance_options, memory_limit) = mock_instance_options();
let instance =
Instance::from_code(CONTRACT, backend, instance_options, memory_limit).unwrap();
assert_eq!(instance.required_features.len(), 0);
}

Expand All @@ -334,7 +335,8 @@ mod tests {
.unwrap();

let backend = mock_backend(&[]);
let instance = Instance::from_code(&wasm, backend, mock_instance_options()).unwrap();
let (instance_options, memory_limit) = mock_instance_options();
let instance = Instance::from_code(&wasm, backend, instance_options, memory_limit).unwrap();
assert_eq!(instance.required_features.len(), 3);
assert!(instance.required_features.contains("nutrients"));
assert!(instance.required_features.contains("sun"));
Expand Down
27 changes: 10 additions & 17 deletions packages/vm/src/modules/in_memory_cache.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,34 @@
use clru::CLruCache;
use wasmer::{Module, Store};
use wasmer::Module;

use crate::{Checksum, Size, VmResult};

const ESTIMATED_MODULE_SIZE: Size = Size::mebi(10);

/// An in-memory module cache
pub struct InMemoryCache {
artifacts: CLruCache<Checksum, Vec<u8>>,
modules: CLruCache<Checksum, Module>,
}

impl InMemoryCache {
/// Creates a new cache with the given size (in bytes)
pub fn new(size: Size) -> Self {
let max_entries = size.0 / ESTIMATED_MODULE_SIZE.0;
InMemoryCache {
artifacts: CLruCache::new(max_entries),
modules: CLruCache::new(max_entries),
}
}

pub fn store(&mut self, checksum: &Checksum, module: Module) -> VmResult<()> {
let serialized_artifact = module.serialize()?;
self.artifacts.put(*checksum, serialized_artifact);
self.modules.put(*checksum, module);
Ok(())
}

/// Looks up a module in the cache and takes its artifact and
/// creates a new module from store and artifact.
pub fn load(&mut self, checksum: &Checksum, store: &Store) -> VmResult<Option<Module>> {
match self.artifacts.get(checksum) {
Some(serialized_artifact) => {
let new_module = unsafe { Module::deserialize(store, &serialized_artifact) }?;
Ok(Some(new_module))
}
pub fn load(&mut self, checksum: &Checksum) -> VmResult<Option<Module>> {
match self.modules.get(checksum) {
Some(module) => Ok(Some(module.clone())),
None => Ok(None),
}
}
Expand All @@ -42,11 +38,10 @@ impl InMemoryCache {
mod tests {
use super::*;
use crate::size::Size;
use crate::wasm_backend::{compile_only, make_runtime_store};
use crate::wasm_backend::compile_only;
use wasmer::{imports, Instance as WasmerInstance};
use wasmer_middlewares::metering::set_remaining_points;

const TESTING_MEMORY_LIMIT: Size = Size::mebi(16);
const TESTING_GAS_LIMIT: u64 = 5_000;

#[test]
Expand All @@ -67,8 +62,7 @@ mod tests {
let checksum = Checksum::generate(&wasm);

// Module does not exist
let store = make_runtime_store(TESTING_MEMORY_LIMIT);
let cache_entry = cache.load(&checksum, &store).unwrap();
let cache_entry = cache.load(&checksum).unwrap();
assert!(cache_entry.is_none());

// Compile module
Expand All @@ -87,8 +81,7 @@ mod tests {
cache.store(&checksum, original).unwrap();

// Load module
let store = make_runtime_store(TESTING_MEMORY_LIMIT);
let cached = cache.load(&checksum, &store).unwrap().unwrap();
let cached = cache.load(&checksum).unwrap().unwrap();

// Ensure cached module can be executed
{
Expand Down
18 changes: 10 additions & 8 deletions packages/vm/src/testing/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,21 +137,23 @@ pub fn mock_instance_with_options(
storage: MockStorage::default(),
querier: MockQuerier::new(&balances),
};
let memory_limit = options.memory_limit;
let options = InstanceOptions {
gas_limit: options.gas_limit,
memory_limit: options.memory_limit,
print_debug: options.print_debug,
};
Instance::from_code(wasm, backend, options).unwrap()
Instance::from_code(wasm, backend, options, memory_limit).unwrap()
}

/// Creates InstanceOptions for testing
pub fn mock_instance_options() -> InstanceOptions {
InstanceOptions {
gas_limit: DEFAULT_GAS_LIMIT,
memory_limit: DEFAULT_MEMORY_LIMIT,
print_debug: DEFAULT_PRINT_DEBUG,
}
pub fn mock_instance_options() -> (InstanceOptions, Size) {
(
InstanceOptions {
gas_limit: DEFAULT_GAS_LIMIT,
print_debug: DEFAULT_PRINT_DEBUG,
},
DEFAULT_MEMORY_LIMIT,
)
}

/// Runs a series of IO tests, hammering especially on allocate and deallocate.
Expand Down

0 comments on commit e69ac7e

Please sign in to comment.