Skip to content

Commit

Permalink
refactor: Remove MultiFrameTrait (#1026)
Browse files Browse the repository at this point in the history
* refactor: Simplify `Prover` trait in `proof` module

- Deleted the `outer_synthesize` method from the `Prover` trait.
- Removed unused import and unused type alias from `src/proof/mod.rs`

* refactor: Refactor generic parameters in proof evaluation traits

- Refactor multiple traits including CEKState, FrameLike, and EvaluationStore among others, reducing the number of generic parameters from two to one.
- Unify all pointer types to `Ptr`, eliminating separation between `ExprPtr` and `ContPtr`, resulting in simplified function signatures and traits.
- Update all references of `ContPtr` to the new unified `Ptr` type throughout the code base for consistency.
- Update test functions in `src/proof/tests/mod.rs` to expect the new `Ptr` type instead of `ContPtr`.
- Adjust the function definitions in `MultiFrameTrait` to align with the new unified `Ptr` generic parameter.

* refactor: Refactor proof module to directly use `Store<F>`, `Ptr`, `Frame`

- Refactored `MultiFrameTrait` related methods in `src/proof/mod.rs` to use `Store<F>`, `Ptr` and `Frame` directly instead of associated types.
- Updated `prove_recursively` method in `RecursiveSNARKTrait` and Prover methods to reflect these changes.
- Incorporated the usage of `Store<F>` in place of `<M>::Store` in `src/proof/supernova.rs` and `src/proof/tests/mod.rs`, leading to adjustments across various function definitions.
- Implemented the option to switch between parallel and sequential computation in the recursive SNARK proofing in `src/proof/nova.rs`.
- In `src/lem/multiframe.rs`, simplified type specifications across various methods to directly use existing types like `Store<F>`, `Frame` and `Ptr`.
- Updated `cache_witness()` function to handle errors more efficiently and carried out general code refactoring and simplification for overall improvement.

* refactor: Refactor proof structures, removing MultiFrame usage

- Removed dependency on `MultiFrameTrait` and `StepCircuit`, `SuperStepCircuit` from various sections, simplifying the imports.
- Simplified the `LurkProof` data structure by removing the `M: MultiFrameTrait` parameter.
- Adapted the `prover` variable type to be more generic across various files and functions such as `end2end_benchmark` and `prove_benchmark`.
- Updated `NovaProver` struct and instantiation, removing `MultiFrameTrait` application. Replaced `MultiFrameTrait` usage with `C1LEM`.
- Refactored proof structures in code, including modifications to function calls, assertions, proof verification and synthesis, shifting towards a simpler or more direct format.
- The changes do not seem to alter the program's fundamental logic, but rather its structuring and instantiation.

* refactor: Refactor SupernovaProver to use C1LEM instead of MultiFrameTrait

- Overhauled the `supernova.rs` code, replacing `MultiFrameTrait` with `nova::C1LEM`
- Removed usage of `MultiFrameTrait` in `SuperNovaProver`, further refining its scope and purpose
- Updated `Prover` code to reflect the change in usage from `MultiFrameTrait` to `C1LEM`
- Enhanced `sha256_nivc.rs` example by directly implementing `SuperNovaProver` without `MultiFrame`, and adding proof size output and verification functionality.

* refactor: Replace MultiFrameTrait with C1LEM across codebase

- Replaced the `MultiFrameTrait` with `C1LEM` across various functions in different files which made `circuit_cache_key` method and other related methods non-generic.
- Updated method calls reflecting changes of generic parameters to use `C1LEM` in place of `MultiFrameTrait` and also in cache handling methods in `supernova.rs` file.
- Removed `MultiFrame` usage in several benchmark methods - namely `end2end_benchmark`, `prove_benchmark`, `prove_compressed_benchmark`, `verify_benchmark`, and `verify_compressed_benchmark`.
- Updated `public_params` function and its related functions to work with changes of replacing `MultiFrameTrait` with `C1LEM`, this included adjusting the return type of `public_params` and modifications in `DiskCache` struct in `public_parameters/mod.rs` file.
- Examples and benchmarks using `public_params` function refactored to no longer require `MultiFrame` as a generic argument, this occurred in `examples/circom.rs`, `benches/fibonacci.rs`, and `benches/sha256.rs` files.
- `DiskCache` struct heavily refactored with the replacement of `MultiFrameTrait` with `C1LEM`, impacting `read` and `write` methods return types in `public_parameters/disk_cache.rs` file.
- The usage of `get_from_disk_cache_or_update_with` and `get_from_mem_cache_or_update_with` functions in `public_parameters/mem_cache.rs` file changed with the adoption of `C1LEM`.
- The `Instance` struct in the `src/public_parameters/instance.rs` was simplified and restructured to no longer depend on `MultiFrameTrait` and its associated methods and return types updated accordingly.

* refactor: Refactor generic functions to specific C1LEM use

- Refactored code, replacing the use of the `M: StepCircuit<F> + MultiFrameTrait<'a, F, C>` generic with the more specific `C1LEM<'a, F, C>` struct.
- Redesigned the `public_params` and `circuits` function outputs to return `C1LEM<'a, F, C>` instead of `M`.
- Observed substantial edits within code used for proof checking, compressed proof verification, and result checking.
- Eliminated usage of `MultiFrameTrait<'a, F, C>` and `MultiFrame` import from function parameters and file imports respectively across a number of files.
- Updated various function calls to accommodate the `C1LEM<'a, F, C>` struct instead of `M`.
- Altered the `public_params` function in `supernova.rs` by updating the return type and modifying setup for non-uniform circuit and public params.

* refactor: Refactor and remove `MultiFrameTrait` across modules

- Removed `MultiFrameTrait` from various files due to its deprecation and switched to direct use of 'MultiFrame' methods.
- Updated 'Provable' and 'Prover' traits to accommodate removal of `MultiFrameTrait`, with relevant modifications in methods `proof` and `evaluate_and_prove`.
- Renamed 'io_to_scalar_vector' function to 'to_scalar_vector' and applied this change in the trait methods.
- Introduced debugging and verification of the circuit's constraint system in 'prove_recursively' function in `nova.rs`.
- The 'MultiFrame' struct underwent significant reorganization to replace 'MultiFrameTrait', including new getter methods, building methods and potential performance improvements in synthesizing frames.
- Reorganized Frame allocation and handling in the `MultiFrame` instances for more effective synthesization.

* chore: clippy
  • Loading branch information
huitseeker authored Jan 9, 2024
1 parent dae844a commit 2542602
Show file tree
Hide file tree
Showing 24 changed files with 922 additions and 1,188 deletions.
15 changes: 7 additions & 8 deletions benches/end2end.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use lurk::{
field::LurkField,
lem::{
eval::{evaluate, evaluate_simple},
multiframe::MultiFrame,
pointers::Ptr,
store::Store,
},
Expand Down Expand Up @@ -64,7 +63,7 @@ fn end2end_benchmark(c: &mut Criterion) {
let lang_pallas_rc = Arc::new(lang_pallas.clone());

let store = Store::default();
let prover: NovaProver<'_, Fq, Coproc<Fq>, MultiFrame<'_, Fq, Coproc<Fq>>> =
let prover: NovaProver<'_, Fq, Coproc<Fq>> =
NovaProver::new(reduction_count, lang_pallas_rc.clone());

// use cached public params
Expand All @@ -74,7 +73,7 @@ fn end2end_benchmark(c: &mut Criterion) {
true,
Kind::NovaPublicParams,
);
let pp = public_parameters::public_params::<_, _, MultiFrame<'_, _, _>>(&instance).unwrap();
let pp = public_parameters::public_params(&instance).unwrap();

let size = (10, 0);
let benchmark_id = BenchmarkId::new("end2end_go_base_nova", format!("_{}_{}", size.0, size.1));
Expand Down Expand Up @@ -245,11 +244,11 @@ fn prove_benchmark(c: &mut Criterion) {
true,
Kind::NovaPublicParams,
);
let pp = public_parameters::public_params::<_, _, MultiFrame<'_, _, _>>(&instance).unwrap();
let pp = public_parameters::public_params(&instance).unwrap();

group.bench_with_input(benchmark_id, &size, |b, &s| {
let ptr = go_base::<Fq>(&store, state.clone(), s.0, s.1);
let prover: NovaProver<'_, Fq, Coproc<Fq>, MultiFrame<'_, Fq, Coproc<Fq>>> =
let prover: NovaProver<'_, Fq, Coproc<Fq>> =
NovaProver::new(reduction_count, lang_pallas_rc.clone());
let frames = evaluate::<Fq, Coproc<Fq>>(None, ptr, &store, limit).unwrap();

Expand Down Expand Up @@ -293,7 +292,7 @@ fn prove_compressed_benchmark(c: &mut Criterion) {
true,
Kind::NovaPublicParams,
);
let pp = public_parameters::public_params::<_, _, MultiFrame<'_, _, _>>(&instance).unwrap();
let pp = public_parameters::public_params(&instance).unwrap();

group.bench_with_input(benchmark_id, &size, |b, &s| {
let ptr = go_base::<Fq>(&store, state.clone(), s.0, s.1);
Expand Down Expand Up @@ -335,7 +334,7 @@ fn verify_benchmark(c: &mut Criterion) {
true,
Kind::NovaPublicParams,
);
let pp = public_parameters::public_params::<_, _, MultiFrame<'_, _, _>>(&instance).unwrap();
let pp = public_parameters::public_params(&instance).unwrap();

let sizes = [(10, 0)];
for size in sizes {
Expand Down Expand Up @@ -387,7 +386,7 @@ fn verify_compressed_benchmark(c: &mut Criterion) {
true,
Kind::NovaPublicParams,
);
let pp = public_parameters::public_params::<_, _, MultiFrame<'_, _, _>>(&instance).unwrap();
let pp = public_parameters::public_params(&instance).unwrap();

let sizes = [(10, 0)];
for size in sizes {
Expand Down
4 changes: 2 additions & 2 deletions benches/fibonacci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::{sync::Arc, time::Duration};

use lurk::{
eval::lang::{Coproc, Lang},
lem::{eval::evaluate, multiframe::MultiFrame, store::Store},
lem::{eval::evaluate, store::Store},
proof::nova::NovaProver,
proof::Prover,
public_parameters::{
Expand Down Expand Up @@ -95,7 +95,7 @@ fn fibonacci_prove<M: measurement::Measurement>(
true,
Kind::NovaPublicParams,
);
let pp = public_params::<_, _, MultiFrame<'_, _, _>>(&instance).unwrap();
let pp = public_params(&instance).unwrap();

// Track the number of `Lurk frames / sec`
let rc = prove_params.reduction_count as u64;
Expand Down
6 changes: 1 addition & 5 deletions benches/public_params.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion, SamplingMode};
use lurk::{
eval::lang::{Coproc, Lang},
lem::multiframe::MultiFrame,
proof::nova,
};
use std::sync::Arc;
Expand All @@ -23,10 +22,7 @@ fn public_params_benchmark(c: &mut Criterion) {

group.bench_function("public_params_nova", |b| {
b.iter(|| {
let result = nova::public_params::<_, _, MultiFrame<'_, _, _>>(
reduction_count,
lang_pallas_rc.clone(),
);
let result = nova::public_params(reduction_count, lang_pallas_rc.clone());
black_box(result)
})
});
Expand Down
9 changes: 4 additions & 5 deletions benches/sha256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use lurk::{
field::LurkField,
lem::{
eval::{evaluate, make_cprocs_funcs_from_lang, make_eval_step_from_config, EvalConfig},
multiframe::MultiFrame,
pointers::Ptr,
store::Store,
},
Expand Down Expand Up @@ -112,13 +111,13 @@ fn sha256_ivc_prove<M: measurement::Measurement>(
let lurk_step = make_eval_step_from_config(&EvalConfig::new_ivc(&lang));

// use cached public params
let instance: Instance<'_, Fr, Sha256Coproc<Fr>, MultiFrame<'_, _, _>> = Instance::new(
let instance: Instance<'_, Fr, Sha256Coproc<Fr>> = Instance::new(
reduction_count,
lang_rc.clone(),
true,
Kind::NovaPublicParams,
);
let pp = public_params::<_, _, MultiFrame<'_, _, _>>(&instance).unwrap();
let pp = public_params(&instance).unwrap();

c.bench_with_input(
BenchmarkId::new(prove_params.name(), arity),
Expand Down Expand Up @@ -199,7 +198,7 @@ fn sha256_ivc_prove_compressed<M: measurement::Measurement>(
true,
Kind::NovaPublicParams,
);
let pp = public_params::<_, _, MultiFrame<'_, _, _>>(&instance).unwrap();
let pp = public_params(&instance).unwrap();

c.bench_with_input(
BenchmarkId::new(prove_params.name(), arity),
Expand Down Expand Up @@ -283,7 +282,7 @@ fn sha256_nivc_prove<M: measurement::Measurement>(
true,
Kind::SuperNovaAuxParams,
);
let pp = supernova_public_params::<_, _, MultiFrame<'_, _, _>>(&instance).unwrap();
let pp = supernova_public_params(&instance).unwrap();

c.bench_with_input(
BenchmarkId::new(prove_params.name(), arity),
Expand Down
5 changes: 2 additions & 3 deletions benches/synthesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use lurk::{
eval::lang::{Coproc, Lang},
field::LurkField,
lem::{eval::evaluate, multiframe::MultiFrame, pointers::Ptr, store::Store},
proof::{supernova::FoldingConfig, MultiFrameTrait},
proof::supernova::FoldingConfig,
state::State,
};

Expand Down Expand Up @@ -55,8 +55,7 @@ fn synthesize<M: measurement::Measurement>(
let folding_config =
Arc::new(FoldingConfig::new_ivc(lang_rc.clone(), *reduction_count));

let multiframe =
MultiFrame::from_frames(&frames, &store, folding_config.clone())[0].clone();
let multiframe = MultiFrame::from_frames(&frames, &store, &folding_config)[0].clone();

b.iter_batched(
|| (multiframe.clone()), // avoid cloning the frames in the benchmark
Expand Down
8 changes: 2 additions & 6 deletions examples/circom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ use std::time::Instant;

use lurk::circuit::gadgets::circom::CircomGadget;
use lurk::circuit::gadgets::pointer::AllocatedPtr;
use lurk::lem::multiframe::MultiFrame;

#[cfg(not(target_arch = "wasm32"))]
use lurk::coprocessor::circom::non_wasm::CircomCoprocessor;
Expand Down Expand Up @@ -110,16 +109,13 @@ fn main() {
let expr = "(.circom_sha256_2)".to_string();
let ptr = store.read_with_default_state(&expr).unwrap();

let nova_prover = NovaProver::<Fr, Sha256Coproc<Fr>, MultiFrame<'_, _, _>>::new(
REDUCTION_COUNT,
lang_rc.clone(),
);
let nova_prover = NovaProver::<Fr, Sha256Coproc<Fr>>::new(REDUCTION_COUNT, lang_rc.clone());

println!("Setting up public parameters...");

let pp_start = Instant::now();
let instance = Instance::new(REDUCTION_COUNT, lang_rc, true, Kind::NovaPublicParams);
let pp = public_params::<_, _, MultiFrame<'_, _, _>>(&instance).unwrap();
let pp = public_params(&instance).unwrap();
let pp_end = pp_start.elapsed();

println!("Public parameters took {pp_end:?}");
Expand Down
7 changes: 2 additions & 5 deletions examples/sha256_ivc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use lurk::{
coprocessor::sha256::{Sha256Coproc, Sha256Coprocessor},
eval::lang::Lang,
field::LurkField,
lem::{multiframe::MultiFrame, pointers::Ptr, store::Store},
lem::{pointers::Ptr, store::Store},
proof::{nova::NovaProver, Prover, RecursiveSNARKTrait},
public_parameters::{
instance::{Instance, Kind},
Expand Down Expand Up @@ -71,10 +71,7 @@ fn main() {
lang.add_coprocessor(cproc_sym, Sha256Coprocessor::new(n));
let lang_rc = Arc::new(lang.clone());

let nova_prover = NovaProver::<Fr, Sha256Coproc<Fr>, MultiFrame<'_, _, _>>::new(
REDUCTION_COUNT,
lang_rc.clone(),
);
let nova_prover = NovaProver::<Fr, Sha256Coproc<Fr>>::new(REDUCTION_COUNT, lang_rc.clone());

println!("Setting up public parameters (rc = {REDUCTION_COUNT})...");

Expand Down
9 changes: 3 additions & 6 deletions examples/sha256_nivc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use lurk::{
field::LurkField,
lem::{
eval::{evaluate, make_cprocs_funcs_from_lang, make_eval_step_from_config, EvalConfig},
multiframe::MultiFrame,
pointers::Ptr,
store::Store,
},
Expand Down Expand Up @@ -80,16 +79,14 @@ fn main() {
let cprocs = make_cprocs_funcs_from_lang(&lang);
let frames = evaluate(Some((&lurk_step, &cprocs, &lang)), call, store, 1000).unwrap();

let supernova_prover = SuperNovaProver::<Fr, Sha256Coproc<Fr>, MultiFrame<'_, _, _>>::new(
REDUCTION_COUNT,
lang_rc.clone(),
);
let supernova_prover =
SuperNovaProver::<Fr, Sha256Coproc<Fr>>::new(REDUCTION_COUNT, lang_rc.clone());

println!("Setting up running claim parameters (rc = {REDUCTION_COUNT})...");
let pp_start = Instant::now();

let instance_primary = Instance::new(REDUCTION_COUNT, lang_rc, true, Kind::SuperNovaAuxParams);
let pp = supernova_public_params::<_, _, MultiFrame<'_, _, _>>(&instance_primary).unwrap();
let pp = supernova_public_params(&instance_primary).unwrap();

let pp_end = pp_start.elapsed();
println!("Running claim parameters took {:?}", pp_end);
Expand Down
3 changes: 1 addition & 2 deletions examples/tp_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,7 @@ fn main() {
let mut data = Vec::with_capacity(rc_vec.len());

for rc in rc_vec.clone() {
let prover: NovaProver<'_, _, _, MultiFrame<'_, _, Coproc<Fr>>> =
NovaProver::new(rc, lang_arc.clone());
let prover: NovaProver<'_, _, _> = NovaProver::new(rc, lang_arc.clone());
println!("Getting public params for rc={rc}");
// TODO: use cache once it's fixed
let pp: PublicParams<_, MultiFrame<'_, _, Coproc<Fr>>> =
Expand Down
33 changes: 8 additions & 25 deletions src/cli/lurk_proof.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use ::nova::{
supernova::{NonUniformCircuit, StepCircuit as SuperStepCircuit},
traits::Engine,
};
use ::nova::traits::Engine;
use abomonation::Abomonation;
use anyhow::{bail, Result};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
Expand All @@ -14,8 +11,7 @@ use crate::{
lem::{pointers::ZPtr, store::Store},
proof::{
nova::{self, CurveCycleEquipped, E1, E2},
supernova::C2,
MultiFrameTrait, RecursiveSNARKTrait,
RecursiveSNARKTrait,
},
public_parameters::{
instance::{Instance, Kind},
Expand Down Expand Up @@ -130,26 +126,21 @@ pub(crate) enum LurkProof<
'a,
F: CurveCycleEquipped,
C: Coprocessor<F> + Serialize + DeserializeOwned,
M: MultiFrameTrait<'a, F, C>,
> where
<<E1<F> as Engine>::Scalar as ff::PrimeField>::Repr: Abomonation,
<<E2<F> as Engine>::Scalar as ff::PrimeField>::Repr: Abomonation,
{
Nova {
proof: nova::Proof<'a, F, C, M>,
proof: nova::Proof<'a, F, C>,
public_inputs: Vec<F>,
public_outputs: Vec<F>,
rc: usize,
lang: Lang<F, C>,
},
}

impl<
'a,
F: CurveCycleEquipped,
C: Coprocessor<F> + 'a + Serialize + DeserializeOwned,
M: MultiFrameTrait<'a, F, C>,
> HasFieldModulus for LurkProof<'a, F, C, M>
impl<'a, F: CurveCycleEquipped, C: Coprocessor<F> + 'a + Serialize + DeserializeOwned>
HasFieldModulus for LurkProof<'a, F, C>
where
<<E1<F> as Engine>::Scalar as ff::PrimeField>::Repr: Abomonation,
<<E2<F> as Engine>::Scalar as ff::PrimeField>::Repr: Abomonation,
Expand All @@ -159,12 +150,8 @@ where
}
}

impl<
'a,
F: CurveCycleEquipped + Serialize,
C: Coprocessor<F> + Serialize + DeserializeOwned,
M: MultiFrameTrait<'a, F, C>,
> LurkProof<'a, F, C, M>
impl<'a, F: CurveCycleEquipped + Serialize, C: Coprocessor<F> + Serialize + DeserializeOwned>
LurkProof<'a, F, C>
where
<<E1<F> as Engine>::Scalar as ff::PrimeField>::Repr: Abomonation,
<<E2<F> as Engine>::Scalar as ff::PrimeField>::Repr: Abomonation,
Expand All @@ -178,11 +165,7 @@ where
impl<
F: CurveCycleEquipped + DeserializeOwned,
C: Coprocessor<F> + Serialize + DeserializeOwned + 'static,
M: MultiFrameTrait<'static, F, C>
+ SuperStepCircuit<F>
+ NonUniformCircuit<E1<F>, E2<F>, M, C2<F>>
+ 'static,
> LurkProof<'static, F, C, M>
> LurkProof<'static, F, C>
where
<<E1<F> as Engine>::Scalar as ff::PrimeField>::Repr: Abomonation,
<<E2<F> as Engine>::Scalar as ff::PrimeField>::Repr: Abomonation,
Expand Down
10 changes: 3 additions & 7 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use std::{
use crate::{
eval::lang::{Coproc, Lang},
field::{LanguageField, LurkField},
lem::{multiframe::MultiFrame, store::Store},
lem::store::Store,
public_parameters::disk_cache::public_params_dir,
public_parameters::instance::Metadata,
};
Expand Down Expand Up @@ -606,14 +606,10 @@ impl Cli {
// TODO: pick a predefined `Lang` according to a CLI parameter
match verify_args.field.unwrap_or_default() {
LanguageField::BN256 => {
LurkProof::<_, _, MultiFrame<'_, _, Coproc<bn256::Fr>>>::verify_proof(
&verify_args.proof_key,
)
LurkProof::<_, Coproc<bn256::Fr>>::verify_proof(&verify_args.proof_key)
}
LanguageField::Pallas => {
LurkProof::<_, _, MultiFrame<'_, _, Coproc<pallas::Scalar>>>::verify_proof(
&verify_args.proof_key,
)
LurkProof::<_, Coproc<pallas::Scalar>>::verify_proof(&verify_args.proof_key)
}
_ => unreachable!(),
}
Expand Down
Loading

1 comment on commit 2542602

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarks

Table of Contents

Overview

This benchmark report shows the Fibonacci GPU benchmark.
NVIDIA L4
Intel(R) Xeon(R) CPU @ 2.20GHz
125.78 GB RAM
Workflow run: https://github.com/lurk-lab/lurk-rs/actions/runs/7465643864

Benchmark Results

LEM Fibonacci Prove - rc = 100

fib-ref=dae844aa2890db7f2d71c897ab4fd3a9d5f1acc0 fib-ref=25426023dfbfc68020a6db99f543a6d2cae69114
num-100 1.73 s (✅ 1.00x) 1.73 s (✅ 1.00x faster)
num-200 3.33 s (✅ 1.00x) 3.32 s (✅ 1.00x faster)

LEM Fibonacci Prove - rc = 600

fib-ref=dae844aa2890db7f2d71c897ab4fd3a9d5f1acc0 fib-ref=25426023dfbfc68020a6db99f543a6d2cae69114
num-100 1.95 s (✅ 1.00x) 1.94 s (✅ 1.01x faster)
num-200 3.34 s (✅ 1.00x) 3.33 s (✅ 1.00x faster)

Made with criterion-table

Please sign in to comment.