Unverified Commit f0747864 authored by Hero Bird's avatar Hero Bird Committed by GitHub

Optimize Key and KeyPtr primitives (#433)

* [chores] add primitives/target to gitignore

* [primitives] add Key2 replacement for Key

# Conflicts:
#	primitives/src/lib.rs

* [primitives] add KeyPtr2 replacement for KeyPtr

* [primitives] add benchmark tests for Key, Key2, KeyPtr and KeyPtr2

* [primitives] rename some benches

* [primitives] inline some KeyPtr methods

This is mainly to make comparison between old and new KeyPtr fairer.

# Conflicts:
#	primitives/src/key_ptr.rs

* [primitives] carve out fallback for big-endian to_bytes

This makes it better testable.

* [primitives] include big-endian fallback procedures into a test

* [primitives] add doc comment to Key2

* [primitives] apply rustfmt

* [primitives] fix clippy warning

* [primitives] add repr(transparent) to Key2

* [primitives] replace AsRef<[u8; 32]> impl with Key2::try_as_bytes method

* [primitives] add SAFETY comment to unsafe block

* [primitives] apply rustfmt

* [primitives] add Display impl to Key2
parent 870095c8
Pipeline #96629 passed with stages
in 6 minutes and 55 seconds
......@@ -3,6 +3,7 @@
/core/target/
/model/target/
/lang/target/
/primitives/target/
/examples/**/target/
/design/
......
......@@ -23,6 +23,9 @@ tiny-keccak = { version = "2.0", features = ["keccak"] }
scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive", "full"] }
scale-info = { version = "0.1", default-features = false, features = ["derive"], optional = true }
[dev-dependencies]
criterion = "0.3.1"
[features]
default = ["std"]
std = [
......@@ -30,3 +33,7 @@ std = [
"scale/std",
"scale-info/std",
]
[[bench]]
name = "bench"
harness = false
// Copyright 2018-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,
Criterion,
};
use ink_primitives::{
Key,
Key2,
KeyPtr,
KeyPtr2,
};
criterion_group!(
bench_key,
bench_key_add_assign_u64,
bench_key2_add_assign_u64,
bench_key2_add_assign_u64_one_ofvl,
bench_key2_add_assign_u64_two_ofvls,
bench_key2_add_assign_u64_three_ofvls,
bench_key2_add_assign_u64_wrap,
);
criterion_group!(
bench_key_ptr,
bench_key_ptr_advance_by,
bench_key_ptr2_advance_by,
bench_key_ptr_advance_by_repeat,
bench_key_ptr2_advance_by_repeat,
);
criterion_main!(bench_key, bench_key_ptr,);
fn bench_key_add_assign_u64(c: &mut Criterion) {
let key = Key([0x00; 32]);
c.bench_function("Key::add_assign(u64)", |b| {
b.iter(|| {
let mut copy = black_box(key);
let _ = black_box(copy += 1u64);
})
});
}
fn bench_key2_add_assign_u64(c: &mut Criterion) {
let key = Key2::from([0x00; 32]);
c.bench_function("Key2::add_assign(u64)", |b| {
b.iter(|| {
let mut copy = black_box(key);
let _ = black_box(copy += 1u64);
})
});
}
fn bench_key2_add_assign_u64_one_ofvl(c: &mut Criterion) {
let key = Key2::from([
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
c.bench_function("Key2::add_assign(u64) - 1 ofvl", |b| {
b.iter(|| {
let mut copy = black_box(key);
let _ = black_box(copy += 1u64);
})
});
}
fn bench_key2_add_assign_u64_two_ofvls(c: &mut Criterion) {
let key = Key2::from([
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
c.bench_function("Key2::add_assign(u64) - 2 ofvls", |b| {
b.iter(|| {
let mut copy = black_box(key);
let _ = black_box(copy += 1u64);
})
});
}
fn bench_key2_add_assign_u64_three_ofvls(c: &mut Criterion) {
let key = Key2::from([
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
c.bench_function("Key2::add_assign(u64) - 3 ofvls", |b| {
b.iter(|| {
let mut copy = black_box(key);
let _ = black_box(copy += 1u64);
})
});
}
fn bench_key2_add_assign_u64_wrap(c: &mut Criterion) {
let key = Key2::from([0xFF; 32]);
c.bench_function("Key2::add_assign(u64) - wrap", |b| {
b.iter(|| {
let mut copy = black_box(key);
let _ = black_box(copy += 1u64);
})
});
}
fn bench_key_ptr_advance_by(c: &mut Criterion) {
let key = Key([0x00; 32]);
c.bench_function("KeyPtr::advance_by copy", |b| {
b.iter(|| {
let mut key_ptr = KeyPtr::from(key.clone());
let _ = black_box(key_ptr.advance_by(1));
})
});
}
fn bench_key_ptr2_advance_by(c: &mut Criterion) {
let key = Key2::from([0x00; 32]);
c.bench_function("KeyPtr2::advance_by copy", |b| {
b.iter(|| {
let mut key_ptr = KeyPtr2::from(key.clone());
let _ = black_box(key_ptr.advance_by(1));
})
});
}
fn bench_key_ptr_advance_by_repeat(c: &mut Criterion) {
let key = Key([0x00; 32]);
let mut key_ptr = KeyPtr::from(key.clone());
c.bench_function("KeyPtr::advance_by reuse", |b| {
b.iter(|| {
let _ = black_box(key_ptr.advance_by(1));
})
});
}
fn bench_key_ptr2_advance_by_repeat(c: &mut Criterion) {
let key = Key2::from([0x00; 32]);
let mut key_ptr = KeyPtr2::from(key.clone());
c.bench_function("KeyPtr2::advance_by reuse", |b| {
b.iter(|| {
let _ = black_box(key_ptr.advance_by(1));
})
});
}
// Copyright 2018-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 core::{
fmt,
ops::AddAssign,
};
/// Key into contract storage.
///
/// Used to identify contract storage cells for read and write operations.
/// Can be compared to a raw pointer and features simple pointer arithmetic.
///
/// # Note
///
/// This is the most low-level primitive to identify contract storage cells.
///
/// # Unsafe
///
/// Prefer using high-level types found in `ink_core` to operate on the contract
/// storage.
#[derive(Copy, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Key([u64; 4]);
impl Key {
fn write_bytes(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "0x")?;
for limb in &self.0 {
write!(f, "_")?;
for byte in &limb.to_le_bytes() {
write!(f, "{:02X}", byte)?;
}
}
Ok(())
}
}
impl fmt::Debug for Key {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Key(")?;
self.write_bytes(f)?;
write!(f, ")")?;
Ok(())
}
}
impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.write_bytes(f)
}
}
impl From<[u8; 32]> for Key {
#[inline]
fn from(bytes: [u8; 32]) -> Self {
if cfg!(target_endian = "little") {
// SAFETY: If the machine has little endian byte ordering we can
// simply transmute the input bytes into the correct `u64`
// byte ordering for the `Key` data structure. Otherwise
// we have to manually convert them via the
// `from_bytes_be_fallback` procedure.
//
// We decided to have the little endian as default format for Key
// instance since WebAssembly dictates little endian byte ordering
// for the execution environment.
Self(unsafe { ::core::mem::transmute::<[u8; 32], [u64; 4]>(bytes) })
} else {
Self::from_bytes_be_fallback(bytes)
}
}
}
impl Key {
/// Creates a new key from the given bytes.
///
/// # Note
///
/// This is a fallback procedure in case the target machine does not have
/// little endian byte ordering.
#[inline]
fn from_bytes_be_fallback(bytes: [u8; 32]) -> Self {
#[inline]
fn carve_out_u64_bytes(bytes: &[u8; 32], offset: u8) -> [u8; 8] {
let o = (offset * 8) as usize;
[
bytes[o],
bytes[o + 1],
bytes[o + 2],
bytes[o + 3],
bytes[o + 4],
bytes[o + 5],
bytes[o + 6],
bytes[o + 7],
]
}
Self([
u64::from_le_bytes(carve_out_u64_bytes(&bytes, 0)),
u64::from_le_bytes(carve_out_u64_bytes(&bytes, 1)),
u64::from_le_bytes(carve_out_u64_bytes(&bytes, 2)),
u64::from_le_bytes(carve_out_u64_bytes(&bytes, 3)),
])
}
/// Tries to return the underlying bytes as slice.
///
/// This only returns `Some` if the execution environment has little-endian
/// byte order.
pub fn try_as_bytes(&self) -> Option<&[u8; 32]> {
if cfg!(target_endian = "little") {
return Some(
// SAFETY: This pointer cast is possible since the outer struct
// (Key) is `repr(transparent)` and since we restrict
// ourselves to little-endian byte ordering. In any other
// case this is invalid which is why return `None` as
// fallback.
unsafe { &*(&self.0 as *const [u64; 4] as *const [u8; 32]) },
)
}
None
}
}
#[cfg(target_endian = "little")]
impl Key {
/// Returns the bytes that are representing the key.
#[inline]
pub fn to_bytes(&self) -> [u8; 32] {
if cfg!(target_endian = "little") {
// SAFETY: This pointer cast is possible since the outer struct
// (Key) is `repr(transparent)` and since we restrict
// ourselves to little-endian byte ordering. In any other
// case this is invalid which is why return `None` as
// fallback.
unsafe { core::mem::transmute::<[u64; 4], [u8; 32]>(self.0) }
} else {
self.to_bytes_be_fallback()
}
}
/// Fallback big-endian procedure to return the underlying bytes of `self`.
fn to_bytes_be_fallback(&self) -> [u8; 32] {
let mut result = [0x00; 32];
for i in 0..4 {
let o = i * 8;
result[o..(o + 8)].copy_from_slice(&self.0[i].to_le_bytes());
}
result
}
}
impl AddAssign<u64> for Key {
#[inline]
#[rustfmt::skip]
fn add_assign(&mut self, rhs: u64) {
let (res_0, ovfl_0) = self.0[0].overflowing_add(rhs);
let (res_1, ovfl_1) = self.0[1].overflowing_add(ovfl_0 as u64);
let (res_2, ovfl_2) = self.0[2].overflowing_add(ovfl_1 as u64);
let (res_3, _ovfl_3) = self.0[3].overflowing_add(ovfl_2 as u64);
self.0[0] = res_0;
self.0[1] = res_1;
self.0[2] = res_2;
self.0[3] = res_3;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_bytes() -> [u8; 32] {
*b"\
\x00\x01\x02\x03\x04\x05\x06\x07\
\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\
\x10\x11\x12\x13\x14\x15\x16\x17\
\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\
"
}
#[test]
fn default_works() {
assert_eq!(<Key as Default>::default().to_bytes(), [0x00; 32]);
}
#[test]
fn debug_works() {
let key = Key::from(test_bytes());
assert_eq!(
format!("{:?}", key),
String::from(
"Key(0x\
_0001020304050607\
_08090A0B0C0D0E0F\
_1011121314151617\
_18191A1B1C1D1E1F\
)"
),
);
}
#[test]
#[rustfmt::skip]
fn from_works() {
let test_bytes = test_bytes();
assert_eq!(Key::from(test_bytes).to_bytes(), test_bytes);
assert_eq!(Key::from_bytes_be_fallback(test_bytes).to_bytes(), test_bytes);
assert_eq!(Key::from(test_bytes).to_bytes_be_fallback(), test_bytes);
assert_eq!(Key::from_bytes_be_fallback(test_bytes).to_bytes_be_fallback(), test_bytes);
}
#[test]
fn add_one_to_zero() {
let bytes = [0x00; 32];
let expected = {
let mut bytes = [0x00; 32];
bytes[0] = 0x01;
bytes
};
let mut key = Key::from(bytes);
key.add_assign(1u64);
assert_eq!(key.to_bytes(), expected);
}
#[test]
fn add_with_ovfl() {
let bytes = *b"\
\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\
";
let expected = {
let mut expected = [0x00; 32];
expected[8] = 0x01;
expected
};
let mut key = Key::from(bytes);
key.add_assign(1u64);
assert_eq!(key.to_bytes(), expected);
}
#[test]
fn add_with_ovfl_2() {
let bytes = *b"\
\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\
\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\
";
let expected = {
let mut expected = [0x00; 32];
expected[16] = 0x01;
expected
};
let mut key = Key::from(bytes);
key.add_assign(1u64);
assert_eq!(key.to_bytes(), expected);
}
#[test]
fn add_with_ovfl_3() {
let bytes = *b"\
\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\
\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\
\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\
\x00\x00\x00\x00\x00\x00\x00\x00\
";
let expected = {
let mut expected = [0x00; 32];
expected[24] = 0x01;
expected
};
let mut key = Key::from(bytes);
key.add_assign(1u64);
assert_eq!(key.to_bytes(), expected);
}
#[test]
fn add_with_wrap() {
let bytes = [0xFF; 32];
let expected = [0x00; 32];
let mut key = Key::from(bytes);
key.add_assign(1u64);
assert_eq!(key.to_bytes(), expected);
}
#[test]
fn add_assign_to_zero() {
for test_value in &[0_u64, 1, 42, 10_000, u32::MAX as u64, u64::MAX] {
let mut key = <Key as Default>::default();
let expected = {
let mut expected = [0x00; 32];
expected[0..8].copy_from_slice(&test_value.to_le_bytes());
expected
};
key.add_assign(*test_value);
assert_eq!(key.to_bytes(), expected);
}
}
}
......@@ -25,6 +25,7 @@ pub struct KeyPtr {
}
impl From<Key> for KeyPtr {
#[inline]
fn from(key: Key) -> Self {
Self { offset: key }
}
......@@ -32,6 +33,7 @@ impl From<Key> for KeyPtr {
impl KeyPtr {
/// Advances the key pointer by the given amount and returns the old value.
#[inline]
pub fn advance_by(&mut self, amount: u64) -> Key {
let copy = self.offset;
self.offset += amount;
......
// 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 crate::Key2 as Key;
/// A key pointer.
///
/// This wraps a base key and provides an interface to mimic pointer arithmetics.
/// Mainly used to coordinate keys through static storage structures.
#[derive(Debug, Copy, Clone)]
pub struct KeyPtr {
/// The underlying offset key.
key: Key,
/// The last shift performed.
last_shift: u64,
}
impl From<Key> for KeyPtr {
#[inline]
fn from(key: Key) -> Self {
Self { key, last_shift: 0 }
}
}
impl KeyPtr {
/// Advances the key pointer by the given amount and returns the old value.
#[inline]
pub fn advance_by(&mut self, new_shift: u64) -> &Key {
let old_shift = core::mem::replace(&mut self.last_shift, new_shift);
self.key += old_shift;
&self.key
}
}
......@@ -26,12 +26,16 @@
mod byte_utils;
pub mod hash;
mod key;
mod key2;
mod key_ptr;
mod key_ptr2;
pub use self::{
key::{
Key,
KeyDiff,
},
key2::Key as Key2,
key_ptr::KeyPtr,
key_ptr2::KeyPtr as KeyPtr2,
};
Markdown is supported
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