Skip to content

Commit 211a30f

Browse files
committed
Merge #266: heavily optimize the Value type
be8d437 value: special-case a bunch of primitive numeric types in from_bits (Andrew Poelstra) 674b272 value: from_bits: take a BitIter rather than an arbitrary bool-iter (Andrew Poelstra) 2691281 value: rewrite Display and CompactIter (Andrew Poelstra) 7cb40f5 value: rewrite module to use a byte vector as the backing store for Value (Andrew Poelstra) d7a4215 types: return arcs from `FinalData` destructuring methods (Andrew Poelstra) ee62770 value: replace as_left/as_right/as_product with to_* variants (Andrew Poelstra) 87992bc value: do some simple optimizations (Andrew Poelstra) 4196ab8 value: add regression test for type with nontrivial sum (Andrew Poelstra) 790b38a value: add tests for round-tripping compact bits (Andrew Poelstra) 8445f51 value: add test for padded/compact len (Andrew Poelstra) eacf3a7 value: add benchmarks (Andrew Poelstra) Pull request description: This PR has many commits but only one I would say is "big". This logic is all pretty-heavily covered by tests, and I added more tests, as well as benchmarks, so hopefully you don't need to review the fiddly bit-manipulation stuff too hard. It also leaves the `value` module in a somewhat messier state than when it started -- we have a new `ValueRef` type which duplicates some functionality of `Value` but isn't exposed in the public API; we now implement `DagLike` on `Value` itself rather than `&Value`, but using it is pretty slow compared to using it on `ValueRef`. The `Eq` and `Ord` traits are now implemented manually, but `Eq` requires a bit of computation and `Ord`, despite being implemented manually, still exposes a pretty-arbitrary ordering on values which have different types. So it is probably worthwhile to follow up on this with a PR that overhauls the public API and tries to provide a cleaner interface. **However**, this PR also introduces some massive speedups. It introduces new benchmarks and has their output in some commit messages. The highlights are as follows: Prior to this PR: ``` benches::bench_value_create_64k ... bench: 2,565,318.90 ns/iter (+/- 40,140.60) benches::bench_value_create_64k_compact ... bench: 214,621,296.60 ns/iter (+/- 23,731,924.55) benches::bench_value_display_64k ... bench: 17,526,983.30 ns/iter (+/- 339,118.95) ```` With this PR: ``` benches::bench_value_create_64k ... bench: 875,039.00 ns/iter (+/- 5,007.70) benches::bench_value_create_64k_compact ... bench: 45,258.88 ns/iter (+/- 169.22) benches::bench_value_display_64k ... bench: 9,685,460.80 ns/iter (+/- 48,509.31) ``` In other words, directly creating a value which is a 64 kilobyte (not kilobit) bitstring used to take 2.5ms; displaying took 17ms; and parsing it from a bit iterator took **214** milliseconds. Now these take 0.875ms, 9ms and 0.045ms, respectively. These are a 2.8, 1.8, and 4700x speedup respectively. The latter operation in particular, creating values from bitstrings and types, has been a major bottleneck in my fuzzer and is the motivation for this PR. ACKs for top commit: uncomputable: ACK be8d437 Tree-SHA512: 315b19c6cb365ad0fdcb8ae734a8834059348b9b9862ef0e84de60db5b26f660f3d41689ec72ef60c6875320dca9952a86a3a7fdff9c02f67d8b32606cd30a90
2 parents 265a44e + be8d437 commit 211a30f

File tree

7 files changed

+865
-190
lines changed

7 files changed

+865
-190
lines changed

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,6 @@ members = ["simpcli", "simplicity-sys", "fuzz"]
4444
# Should be manually/separately tested since it has a massive dep tree
4545
# and not follow MSRV
4646
exclude = ["jets-bench"]
47+
48+
[lints.rust]
49+
unexpected_cfgs = { level = "deny", check-cfg = ['cfg(bench)'] }

README.md

+19
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,22 @@ The MSRV of this crate is **1.63.0**.
1212
Some of the jet files in the library are auto-generated from Haskell code. These can be updated `update_jets.sh`. This requires the user has `cabal` and other necessary things that are required to build simplicity haskell. Instructions for those can be found in the simplicity repository.
1313

1414
This script also checks that the internal vendored version of simplicity has the same git hash as of the version from which we are auto-generating the code. If this is not the case, the script will fail. This is because we only vendor minimal required C simplicity code and not the entire simplicity repo.
15+
16+
# Benchmarking
17+
18+
There are two sets of benchmarks in this codebase. First, there is the `jets-bench`
19+
sub-crate which uses criterion to collect statistics about jet performance. These
20+
benchmarks are specifically targeted at the C jets and are intended to estimate
21+
consensus costs.
22+
23+
See `jets-bench/README.md` for information about running these benchmarks.
24+
25+
The second set of benchmarks are benchmarks for this library's performance. These
26+
are used to track performance of this library itself. These can be run with
27+
28+
```
29+
RUSTFLAGS=--cfg=bench cargo +nightly bench
30+
```
31+
32+
The custom `cfg` flag is used to prevent nightly-only code from polluting ordinary
33+
code.

src/bit_encoding/bititer.rs

+11
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ pub enum u2 {
6969
_3,
7070
}
7171

72+
impl From<u2> for u8 {
73+
fn from(s: u2) -> u8 {
74+
match s {
75+
u2::_0 => 0,
76+
u2::_1 => 1,
77+
u2::_2 => 2,
78+
u2::_3 => 3,
79+
}
80+
}
81+
}
82+
7283
/// Bitwise iterator formed from a wrapped bytewise iterator. Bytes are
7384
/// interpreted big-endian, i.e. MSB is returned first
7485
#[derive(Debug)]

src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// SPDX-License-Identifier: CC0-1.0
22

3+
#![cfg_attr(bench, feature(test))]
34
#![allow(
45
// we use `bool` to represent bits and frequentely assert_eq against them
56
clippy::bool_assert_comparison,
@@ -23,6 +24,9 @@ pub extern crate hashes;
2324
/// Re-export of hex crate
2425
pub extern crate hex;
2526

27+
#[cfg(bench)]
28+
extern crate test;
29+
2630
#[macro_use]
2731
mod macros;
2832

src/node/mod.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,14 @@ pub trait CoreConstructible: Sized {
190190
/// The expression is minimized by using as many word jets as possible.
191191
fn scribe(ctx: &types::Context, value: &Value) -> Self {
192192
#[derive(Debug, Clone)]
193-
enum Task<'a> {
194-
Process(&'a Value),
193+
enum Task {
194+
Process(Value),
195195
MakeLeft,
196196
MakeRight,
197197
MakeProduct,
198198
}
199199

200-
let mut input = vec![Task::Process(value)];
200+
let mut input = vec![Task::Process(value.shallow_clone())];
201201
let mut output = vec![];
202202
while let Some(top) = input.pop() {
203203
match top {
@@ -206,13 +206,13 @@ pub trait CoreConstructible: Sized {
206206
output.push(Self::unit(ctx));
207207
} else if let Some(word) = value.to_word() {
208208
output.push(Self::const_word(ctx, word));
209-
} else if let Some(left) = value.as_left() {
209+
} else if let Some(left) = value.to_left() {
210210
input.push(Task::MakeLeft);
211211
input.push(Task::Process(left));
212-
} else if let Some(right) = value.as_right() {
212+
} else if let Some(right) = value.to_right() {
213213
input.push(Task::MakeRight);
214214
input.push(Task::Process(right));
215-
} else if let Some((left, right)) = value.as_product() {
215+
} else if let Some((left, right)) = value.to_product() {
216216
input.push(Task::MakeProduct);
217217
input.push(Task::Process(right));
218218
input.push(Task::Process(left));

src/types/final_data.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -232,17 +232,17 @@ impl Final {
232232
}
233233

234234
/// Access the inner types of a sum type.
235-
pub fn as_sum(&self) -> Option<(&Self, &Self)> {
235+
pub fn as_sum(&self) -> Option<(&Arc<Self>, &Arc<Self>)> {
236236
match &self.bound {
237-
CompleteBound::Sum(left, right) => Some((left.as_ref(), right.as_ref())),
237+
CompleteBound::Sum(left, right) => Some((left, right)),
238238
_ => None,
239239
}
240240
}
241241

242242
/// Access the inner types of a product type.
243-
pub fn as_product(&self) -> Option<(&Self, &Self)> {
243+
pub fn as_product(&self) -> Option<(&Arc<Self>, &Arc<Self>)> {
244244
match &self.bound {
245-
CompleteBound::Product(left, right) => Some((left.as_ref(), right.as_ref())),
245+
CompleteBound::Product(left, right) => Some((left, right)),
246246
_ => None,
247247
}
248248
}

0 commit comments

Comments
 (0)