mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-27 15:07:59 +00:00
73d9ae3284
* trie state cache
* Also cache missing access on read.
* fix comp
* bis
* fix
* use has_lru
* remove local storage cache on size 0.
* No cache.
* local cache only
* trie cache and local cache
* storage cache (with local)
* trie cache no local cache
* Add state access benchmark
* Remove warnings etc
* Add trie cache benchmark
* No extra "clone" required
* Change benchmark to use multiple blocks
* Use patches
* Integrate shitty implementation
* More stuff
* Revert "Merge branch 'master' into trie_state_cache"
This reverts commit 947cd8e6d43fced10e21b76d5b92ffa57b57c318, reversing
changes made to 29ff036463.
* Improve benchmark
* Adapt to latest changes
* Adapt to changes in trie
* Add a test that uses iterator
* Start fixing it
* Remove obsolete file
* Make it compile
* Start rewriting the trie node cache
* More work on the cache
* More docs and code etc
* Make data cache an optional
* Tests
* Remove debug stuff
* Recorder
* Some docs and a simple test for the recorder
* Compile fixes
* Make it compile
* More fixes
* More fixes
* Fix fix fix
* Make sure cache and recorder work together for basic stuff
* Test that data caching and recording works
* Test `TrieDBMut` with caching
* Try something
* Fixes, fixes, fixes
* Forward the recorder
* Make it compile
* Use recorder in more places
* Switch to new `with_optional_recorder` fn
* Refactor and cleanups
* Move `ProvingBackend` tests
* Simplify
* Move over all functionality to the essence
* Fix compilation
* Implement estimate encoded size for StorageProof
* Start using the `cache` everywhere
* Use the cache everywhere
* Fix compilation
* Fix tests
* Adds `TrieBackendBuilder` and enhances the tests
* Ensure that recorder drain checks that values are found as expected
* Switch over to `TrieBackendBuilder`
* Start fixing the problem with child tries and recording
* Fix recording of child tries
* Make it compile
* Overwrite `storage_hash` in `TrieBackend`
* Add `storage_cache` to the benchmarks
* Fix `no_std` build
* Speed up cache lookup
* Extend the state access benchmark to also hash a runtime
* Fix build
* Fix compilation
* Rewrite value cache
* Add lru cache
* Ensure that the cache lru works
* Value cache should not be optional
* Add support for keeping the shared node cache in its bounds
* Make the cache configurable
* Check that the cache respects the bounds
* Adds a new test
* Fixes
* Docs and some renamings
* More docs
* Start using the new recorder
* Fix more code
* Take `self` argument
* Remove warnings
* Fix benchmark
* Fix accounting
* Rip off the state cache
* Start fixing fallout after removing the state cache
* Make it compile after trie changes
* Fix test
* Add some logging
* Some docs
* Some fixups and clean ups
* Fix benchmark
* Remove unneeded file
* Use git for patching
* Make CI happy
* Update primitives/trie/Cargo.toml
Co-authored-by: Koute <koute@users.noreply.github.com>
* Update primitives/state-machine/src/trie_backend.rs
Co-authored-by: cheme <emericchevalier.pro@gmail.com>
* Introduce new `AsTrieBackend` trait
* Make the LocalTrieCache not clonable
* Make it work in no_std and add docs
* Remove duplicate dependency
* Switch to ahash for better performance
* Speedup value cache merge
* Output errors on underflow
* Ensure the internal LRU map doesn't grow too much
* Use const fn to calculate the value cache element size
* Remove cache configuration
* Fix
* Clear the cache in between for more testing
* Try to come up with a failing test case
* Make the test fail
* Fix the child trie recording
* Make everything compile after the changes to trie
* Adapt to latest trie-db changes
* Fix on stable
* Update primitives/trie/src/cache.rs
Co-authored-by: cheme <emericchevalier.pro@gmail.com>
* Fix wrong merge
* Docs
* Fix warnings
* Cargo.lock
* Bump pin-project
* Fix warnings
* Switch to released crate version
* More fixes
* Make clippy and rustdocs happy
* More clippy
* Print error when using deprecated `--state-cache-size`
* 🤦
* Fixes
* Fix storage_hash linkings
* Update client/rpc/src/dev/mod.rs
Co-authored-by: Arkadiy Paronyan <arkady.paronyan@gmail.com>
* Review feedback
* encode bound
* Rework the shared value cache
Instead of using a `u64` to represent the key we now use an `Arc<[u8]>`. This arc is also stored in
some extra `HashSet`. We store the key are in an extra `HashSet` to de-duplicate the keys accross
different storage roots. When the latest key usage is dropped in the lru, we also remove the key
from the `HashSet`.
* Improve of the cache by merging the old and new solution
* FMT
* Please stop coming back all the time :crying:
* Update primitives/trie/src/cache/shared_cache.rs
Co-authored-by: Arkadiy Paronyan <arkady.paronyan@gmail.com>
* Fixes
* Make clippy happy
* Ensure we don't deadlock
* Only use one lock to simplify the code
* Do not depend on `Hasher`
* Fix tests
* FMT
* Clippy 🤦
Co-authored-by: cheme <emericchevalier.pro@gmail.com>
Co-authored-by: Koute <koute@users.noreply.github.com>
Co-authored-by: Arkadiy Paronyan <arkady.paronyan@gmail.com>
371 lines
9.8 KiB
Rust
371 lines
9.8 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
//! Trie benchmark (integrated).
|
|
|
|
use hash_db::Prefix;
|
|
use kvdb::KeyValueDB;
|
|
use lazy_static::lazy_static;
|
|
use rand::Rng;
|
|
use sp_state_machine::Backend as _;
|
|
use sp_trie::{trie_types::TrieDBMutBuilderV1, TrieMut as _};
|
|
use std::{borrow::Cow, collections::HashMap, sync::Arc};
|
|
|
|
use node_primitives::Hash;
|
|
|
|
use crate::{
|
|
core::{self, Mode, Path},
|
|
generator::generate_trie,
|
|
simple_trie::SimpleTrie,
|
|
tempdb::{DatabaseType, TempDatabase},
|
|
};
|
|
|
|
pub const SAMPLE_SIZE: usize = 100;
|
|
pub const TEST_WRITE_SIZE: usize = 128;
|
|
|
|
pub type KeyValue = (Vec<u8>, Vec<u8>);
|
|
pub type KeyValues = Vec<KeyValue>;
|
|
|
|
#[derive(Clone, Copy, Debug, derive_more::Display)]
|
|
pub enum DatabaseSize {
|
|
#[display(fmt = "empty")]
|
|
Empty,
|
|
#[display(fmt = "smallest")]
|
|
Smallest,
|
|
#[display(fmt = "small")]
|
|
Small,
|
|
#[display(fmt = "medium")]
|
|
Medium,
|
|
#[display(fmt = "large")]
|
|
Large,
|
|
#[display(fmt = "huge")]
|
|
Huge,
|
|
}
|
|
|
|
lazy_static! {
|
|
static ref KUSAMA_STATE_DISTRIBUTION: SizePool =
|
|
SizePool::from_histogram(crate::state_sizes::KUSAMA_STATE_DISTRIBUTION);
|
|
}
|
|
|
|
impl DatabaseSize {
|
|
/// Should be multiple of SAMPLE_SIZE!
|
|
fn keys(&self) -> usize {
|
|
let val = match *self {
|
|
Self::Empty => 200, // still need some keys to query
|
|
Self::Smallest => 1_000,
|
|
Self::Small => 10_000,
|
|
Self::Medium => 100_000,
|
|
Self::Large => 200_000,
|
|
Self::Huge => 1_000_000,
|
|
};
|
|
|
|
assert_eq!(val % SAMPLE_SIZE, 0);
|
|
|
|
val
|
|
}
|
|
}
|
|
|
|
fn pretty_print(v: usize) -> String {
|
|
let mut print = String::new();
|
|
for (idx, val) in v.to_string().chars().rev().enumerate() {
|
|
if idx != 0 && idx % 3 == 0 {
|
|
print.insert(0, ',');
|
|
}
|
|
print.insert(0, val);
|
|
}
|
|
print
|
|
}
|
|
|
|
pub struct TrieReadBenchmarkDescription {
|
|
pub database_size: DatabaseSize,
|
|
pub database_type: DatabaseType,
|
|
}
|
|
|
|
pub struct TrieReadBenchmark {
|
|
database: TempDatabase,
|
|
root: Hash,
|
|
warmup_keys: KeyValues,
|
|
query_keys: KeyValues,
|
|
database_type: DatabaseType,
|
|
}
|
|
|
|
impl core::BenchmarkDescription for TrieReadBenchmarkDescription {
|
|
fn path(&self) -> Path {
|
|
let mut path = Path::new(&["trie", "read"]);
|
|
path.push(&format!("{}", self.database_size));
|
|
path
|
|
}
|
|
|
|
fn setup(self: Box<Self>) -> Box<dyn core::Benchmark> {
|
|
let mut database = TempDatabase::new();
|
|
|
|
let mut rng = rand::thread_rng();
|
|
let warmup_prefix = KUSAMA_STATE_DISTRIBUTION.key(&mut rng);
|
|
|
|
let mut key_values = KeyValues::new();
|
|
let mut warmup_keys = KeyValues::new();
|
|
let mut query_keys = KeyValues::new();
|
|
let every_x_key = self.database_size.keys() / SAMPLE_SIZE;
|
|
for idx in 0..self.database_size.keys() {
|
|
let kv = (
|
|
KUSAMA_STATE_DISTRIBUTION.key(&mut rng).to_vec(),
|
|
KUSAMA_STATE_DISTRIBUTION.value(&mut rng),
|
|
);
|
|
if idx % every_x_key == 0 {
|
|
// warmup keys go to separate tree with high prob
|
|
let mut actual_warmup_key = warmup_prefix.clone();
|
|
actual_warmup_key[16..].copy_from_slice(&kv.0[16..]);
|
|
warmup_keys.push((actual_warmup_key.clone(), kv.1.clone()));
|
|
key_values.push((actual_warmup_key.clone(), kv.1.clone()));
|
|
} else if idx % every_x_key == 1 {
|
|
query_keys.push(kv.clone());
|
|
}
|
|
|
|
key_values.push(kv)
|
|
}
|
|
|
|
assert_eq!(warmup_keys.len(), SAMPLE_SIZE);
|
|
assert_eq!(query_keys.len(), SAMPLE_SIZE);
|
|
|
|
let root = generate_trie(database.open(self.database_type), key_values);
|
|
|
|
Box::new(TrieReadBenchmark {
|
|
database,
|
|
root,
|
|
warmup_keys,
|
|
query_keys,
|
|
database_type: self.database_type,
|
|
})
|
|
}
|
|
|
|
fn name(&self) -> Cow<'static, str> {
|
|
format!(
|
|
"Trie read benchmark({:?} database ({} keys), db_type: {:?})",
|
|
self.database_size,
|
|
pretty_print(self.database_size.keys()),
|
|
self.database_type,
|
|
)
|
|
.into()
|
|
}
|
|
}
|
|
|
|
struct Storage(Arc<dyn KeyValueDB>);
|
|
|
|
impl sp_state_machine::Storage<sp_core::Blake2Hasher> for Storage {
|
|
fn get(&self, key: &Hash, prefix: Prefix) -> Result<Option<Vec<u8>>, String> {
|
|
let key = sp_trie::prefixed_key::<sp_core::Blake2Hasher>(key, prefix);
|
|
self.0.get(0, &key).map_err(|e| format!("Database backend error: {:?}", e))
|
|
}
|
|
}
|
|
|
|
impl core::Benchmark for TrieReadBenchmark {
|
|
fn run(&mut self, mode: Mode) -> std::time::Duration {
|
|
let mut db = self.database.clone();
|
|
|
|
let storage: Arc<dyn sp_state_machine::Storage<sp_core::Blake2Hasher>> =
|
|
Arc::new(Storage(db.open(self.database_type)));
|
|
|
|
let trie_backend = sp_state_machine::TrieBackendBuilder::new(storage, self.root).build();
|
|
for (warmup_key, warmup_value) in self.warmup_keys.iter() {
|
|
let value = trie_backend
|
|
.storage(&warmup_key[..])
|
|
.expect("Failed to get key: db error")
|
|
.expect("Warmup key should exist");
|
|
|
|
// sanity for warmup keys
|
|
assert_eq!(&value, warmup_value);
|
|
}
|
|
|
|
if mode == Mode::Profile {
|
|
std::thread::park_timeout(std::time::Duration::from_secs(3));
|
|
}
|
|
|
|
let started = std::time::Instant::now();
|
|
for (key, _) in self.query_keys.iter() {
|
|
let _ = trie_backend.storage(&key[..]);
|
|
}
|
|
let elapsed = started.elapsed();
|
|
|
|
if mode == Mode::Profile {
|
|
std::thread::park_timeout(std::time::Duration::from_secs(1));
|
|
}
|
|
|
|
elapsed / (SAMPLE_SIZE as u32)
|
|
}
|
|
}
|
|
|
|
pub struct TrieWriteBenchmarkDescription {
|
|
pub database_size: DatabaseSize,
|
|
pub database_type: DatabaseType,
|
|
}
|
|
|
|
impl core::BenchmarkDescription for TrieWriteBenchmarkDescription {
|
|
fn path(&self) -> Path {
|
|
let mut path = Path::new(&["trie", "write"]);
|
|
path.push(&format!("{}", self.database_size));
|
|
path
|
|
}
|
|
|
|
fn setup(self: Box<Self>) -> Box<dyn core::Benchmark> {
|
|
let mut database = TempDatabase::new();
|
|
|
|
let mut rng = rand::thread_rng();
|
|
let warmup_prefix = KUSAMA_STATE_DISTRIBUTION.key(&mut rng);
|
|
|
|
let mut key_values = KeyValues::new();
|
|
let mut warmup_keys = KeyValues::new();
|
|
let every_x_key = self.database_size.keys() / SAMPLE_SIZE;
|
|
for idx in 0..self.database_size.keys() {
|
|
let kv = (
|
|
KUSAMA_STATE_DISTRIBUTION.key(&mut rng).to_vec(),
|
|
KUSAMA_STATE_DISTRIBUTION.value(&mut rng),
|
|
);
|
|
if idx % every_x_key == 0 {
|
|
// warmup keys go to separate tree with high prob
|
|
let mut actual_warmup_key = warmup_prefix.clone();
|
|
actual_warmup_key[16..].copy_from_slice(&kv.0[16..]);
|
|
warmup_keys.push((actual_warmup_key.clone(), kv.1.clone()));
|
|
key_values.push((actual_warmup_key.clone(), kv.1.clone()));
|
|
}
|
|
|
|
key_values.push(kv)
|
|
}
|
|
|
|
assert_eq!(warmup_keys.len(), SAMPLE_SIZE);
|
|
|
|
let root = generate_trie(database.open(self.database_type), key_values);
|
|
|
|
Box::new(TrieWriteBenchmark {
|
|
database,
|
|
root,
|
|
warmup_keys,
|
|
database_type: self.database_type,
|
|
})
|
|
}
|
|
|
|
fn name(&self) -> Cow<'static, str> {
|
|
format!(
|
|
"Trie write benchmark({:?} database ({} keys), db_type = {:?})",
|
|
self.database_size,
|
|
pretty_print(self.database_size.keys()),
|
|
self.database_type,
|
|
)
|
|
.into()
|
|
}
|
|
}
|
|
|
|
struct TrieWriteBenchmark {
|
|
database: TempDatabase,
|
|
root: Hash,
|
|
warmup_keys: KeyValues,
|
|
database_type: DatabaseType,
|
|
}
|
|
|
|
impl core::Benchmark for TrieWriteBenchmark {
|
|
fn run(&mut self, mode: Mode) -> std::time::Duration {
|
|
let mut rng = rand::thread_rng();
|
|
let mut db = self.database.clone();
|
|
let kvdb = db.open(self.database_type);
|
|
|
|
let mut new_root = self.root;
|
|
|
|
let mut overlay = HashMap::new();
|
|
let mut trie = SimpleTrie { db: kvdb.clone(), overlay: &mut overlay };
|
|
let mut trie_db_mut = TrieDBMutBuilderV1::from_existing(&mut trie, &mut new_root).build();
|
|
|
|
for (warmup_key, warmup_value) in self.warmup_keys.iter() {
|
|
let value = trie_db_mut
|
|
.get(&warmup_key[..])
|
|
.expect("Failed to get key: db error")
|
|
.expect("Warmup key should exist");
|
|
|
|
// sanity for warmup keys
|
|
assert_eq!(&value, warmup_value);
|
|
}
|
|
|
|
let test_key = random_vec(&mut rng, 32);
|
|
let test_val = random_vec(&mut rng, TEST_WRITE_SIZE);
|
|
|
|
if mode == Mode::Profile {
|
|
std::thread::park_timeout(std::time::Duration::from_secs(3));
|
|
}
|
|
|
|
let started = std::time::Instant::now();
|
|
|
|
trie_db_mut.insert(&test_key, &test_val).expect("Should be inserted ok");
|
|
trie_db_mut.commit();
|
|
drop(trie_db_mut);
|
|
|
|
let mut transaction = kvdb.transaction();
|
|
for (key, value) in overlay.into_iter() {
|
|
match value {
|
|
Some(value) => transaction.put(0, &key[..], &value[..]),
|
|
None => transaction.delete(0, &key[..]),
|
|
}
|
|
}
|
|
kvdb.write(transaction).expect("Failed to write transaction");
|
|
|
|
let elapsed = started.elapsed();
|
|
|
|
// sanity check
|
|
assert!(new_root != self.root);
|
|
|
|
if mode == Mode::Profile {
|
|
std::thread::park_timeout(std::time::Duration::from_secs(1));
|
|
}
|
|
|
|
elapsed
|
|
}
|
|
}
|
|
|
|
fn random_vec<R: Rng>(rng: &mut R, len: usize) -> Vec<u8> {
|
|
let mut val = vec![0u8; len];
|
|
rng.fill_bytes(&mut val[..]);
|
|
val
|
|
}
|
|
|
|
struct SizePool {
|
|
distribution: std::collections::BTreeMap<u32, u32>,
|
|
total: u32,
|
|
}
|
|
|
|
impl SizePool {
|
|
fn from_histogram(h: &[(u32, u32)]) -> SizePool {
|
|
let mut distribution = std::collections::BTreeMap::default();
|
|
let mut total = 0;
|
|
for (size, count) in h {
|
|
total += count;
|
|
distribution.insert(total, *size);
|
|
}
|
|
SizePool { distribution, total }
|
|
}
|
|
|
|
fn value<R: Rng>(&self, rng: &mut R) -> Vec<u8> {
|
|
let sr = (rng.next_u64() % self.total as u64) as u32;
|
|
let mut range = self
|
|
.distribution
|
|
.range((std::ops::Bound::Included(sr), std::ops::Bound::Unbounded));
|
|
let size = *range.next().unwrap().1 as usize;
|
|
random_vec(rng, size)
|
|
}
|
|
|
|
fn key<R: Rng>(&self, rng: &mut R) -> Vec<u8> {
|
|
random_vec(rng, 32)
|
|
}
|
|
}
|