Unverified Commit e96513a6 authored by Michael Müller's avatar Michael Müller Committed by GitHub
Browse files

[core] Optimize dynamic storage allocator (#472)

* [core] Fix typos

* [core] Add BitStash::is_completely_full()

* [core] Split logic and get rid of loop

* [core] Add benchmarks for storage2::BitStash

* [core] Shorten code

* [core] Remove outcommented code

* [core] Get rid of warnings

* [core] Apply cargo fmt

* [core] Remove unnecessary mutable

* [core] Remove unnecessary check

* [core] Add more storage2::BitStash benchmarks

* [core] Fix benches (panics for empty_cache::one_put)

* [core] Fix empty_cache::one_put bench

* Revert "[core] Remove outcommented code"

This reverts commit 98a74324.

* [core] Improve benchmark labels

* [core] Use iter_batched_ref consistently

* [core] Remove unnecessary black_box

* Revert "Revert "[core] Remove outcommented code""

This reverts commit aaf24713.

* [core] Satisfy clippy
parent 68ac2124
Pipeline #100133 failed with stages
in 10 minutes and 1 second
......@@ -85,3 +85,7 @@ harness = false
[[bench]]
name = "bench_stash"
harness = false
[[bench]]
name = "bench_bitstash"
harness = false
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use criterion::{
black_box,
criterion_group,
criterion_main,
BatchSize,
Criterion,
};
use ink_core::{
env,
storage2::{
collections::BitStash,
traits::{
KeyPtr,
SpreadLayout,
},
},
};
use ink_primitives::Key;
const BENCH_ALLOCATIONS: u32 = 100_000;
criterion_group!(populated_cache, bench_populated_cache,);
criterion_group!(empty_cache, bench_empty_cache,);
criterion_main!(populated_cache, empty_cache,);
/// Creates a `BitStash` and pushes it to the contract storage.
fn push_stash() {
let stash = BitStash::default();
let root_key = Key::from([0x00; 32]);
SpreadLayout::push_spread(&stash, &mut KeyPtr::from(root_key));
}
/// Creates a `BitStash` and pushes it to the contract storage.
fn push_stash_by_ref(stash: &BitStash) {
let root_key = Key::from([0x00; 32]);
SpreadLayout::push_spread(stash, &mut KeyPtr::from(root_key));
}
/// Pulls a lazily loading `BitStash` instance from the contract storage.
fn pull_stash() -> BitStash {
let root_key = Key::from([0x00; 32]);
<BitStash as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key))
}
/// Executes only a single `put` operation on the stash.
pub fn one_put(stash: &mut BitStash) {
black_box(stash.put());
}
/// Returns a stash on which `100_000` `put` operations have been executed.
fn create_large_stash() -> BitStash {
let mut stash = BitStash::default();
for _ in 0..100_000 {
stash.put();
}
stash
}
mod populated_cache {
use super::*;
/// Executes `put` operations on a new `BitStash` exactly `BENCH_ALLOCATIONS` times.
pub fn fill_bitstash() {
let mut stash = BitStash::default();
for _ in 0..BENCH_ALLOCATIONS {
black_box(stash.put());
}
}
}
fn bench_populated_cache(c: &mut Criterion) {
let mut group = c.benchmark_group("Bench: populated cache");
group.bench_function("fill_bitstash", |b| {
b.iter(|| populated_cache::fill_bitstash())
});
group.bench_function("one_put", |b| {
b.iter_batched_ref(
|| create_large_stash(),
|stash| one_put(stash),
BatchSize::SmallInput,
)
});
group.finish();
}
mod empty_cache {
use super::*;
/// Executes `put` operations on a new `BitStash` exactly `BENCH_ALLOCATIONS` times.
pub fn fill_bitstash() {
push_stash();
let mut stash = pull_stash();
for _ in 0..BENCH_ALLOCATIONS {
black_box(stash.put());
}
}
}
/// In this case we lazily instantiate a `BitStash` by first creating and storing
/// into the contract storage. We then load the stash from storage lazily in each
/// benchmark iteration.
fn bench_empty_cache(c: &mut Criterion) {
let _ = env::test::run_test::<env::DefaultEnvTypes, _>(|_| {
let mut group = c.benchmark_group("Bench: empty cache");
group
.bench_function("fill_bitstash", |b| b.iter(|| empty_cache::fill_bitstash()));
group.bench_function("one_put", |b| {
b.iter_batched_ref(
|| {
let stash = create_large_stash();
push_stash_by_ref(&stash);
pull_stash()
},
|stash| one_put(stash),
BatchSize::SmallInput,
)
});
group.finish();
Ok(())
})
.unwrap();
}
......@@ -32,7 +32,7 @@
//! This way we can reduce the problem of finding another region in our storage
//! that fits certain requirements (e.g. a minimum size) to the problem of
//! finding another uniform slot. Since we are on 32-bit WebAssembly we have
//! memory limitations that makes it impractical to have more than 2^32 dynamic
//! memory limitations that make it impractical to have more than 2^32 dynamic
//! allocated entities and so we can create another limitation for having a
//! total of 2^32 dynamic allocations at any point in time.
//! This enables us to have 32-bit keys instead of 256-bit keys.
......
......@@ -69,18 +69,27 @@ impl CountFree {
/// Returns the position of the first free `u8` in the free counts.
///
/// Returns `None` if all counts are `0xFF`.
pub fn position_first_zero(&mut self) -> Option<u8> {
for (i, count) in self.counts.iter_mut().enumerate() {
if !self.full.is_full(i as u8) {
if *count == !0 {
self.full.set_full(i as u8);
} else {
*count += 1;
}
return Some(i as u8)
}
pub fn position_first_zero(&self) -> Option<u8> {
let i = (!self.full.0).leading_zeros();
if i == 32 {
return None
}
Some(i as u8)
}
/// Increases the number of set bits for the given index.
///
/// # Panics
///
/// - If the given index is out of bounds.
/// - If the increment would cause an overflow.
pub fn inc(&mut self, index: usize) {
assert!(index < 32, "index is out of bounds");
if self.counts[index] == !0 {
self.full.set_full(index as u8);
} else {
self.counts[index] += 1;
}
None
}
/// Decreases the number of set bits for the given index.
......
......@@ -34,7 +34,7 @@ type Index = u32;
/// A stash for bits operating on the contract storage.
///
/// Allows to efficienty put and take bits and
/// Allows to efficiently put and take bits and
/// stores the underlying bits in an extremely compressed format.
#[derive(Debug, Default, PartialEq, Eq)]
pub struct BitStash {
......@@ -76,6 +76,7 @@ impl BitStash {
// The counts list consists of packs of 32 counts per element.
for (n, counts) in self.counts.iter_mut().enumerate() {
if let Some(i) = counts.position_first_zero() {
counts.inc(i as usize);
let n = n as u64;
let i = i as u64;
return Some(n * (32 * 256) + i * 256)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment