Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: added simple benchmarks and tooling #100

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Benchmarks

on:
push:
branches:
- master
pull_request:

jobs:
test:
name: Benchmark library
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4

- name: Install Nargo
uses: noir-lang/[email protected]
with:
toolchain: 1.0.0-beta.0

- name: Install bb
run: |
npm install -g bbup
bbup -nv 1.0.0-beta.0
- name: Build Noir benchmark programs
run: nargo export

- name: Generate gates report
run: ./scripts/build-gates-report.sh
env:
BACKEND: /home/runner/.bb/bb

- name: Compare gates reports
id: gates_diff
uses: noir-lang/noir-gates-diff@1931aaaa848a1a009363d6115293f7b7fc72bb87
with:
report: gates_report.json
summaryQuantile: 0.9 # only display the 10% most significant circuit size diffs in the summary (defaults to 20%)

- name: Add gates diff to sticky comment
if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target'
uses: marocchino/sticky-pull-request-comment@v2
with:
# delete the comment in case changes no longer impact circuit sizes
delete: ${{ !steps.gates_diff.outputs.markdown }}
message: ${{ steps.gates_diff.outputs.markdown }}
11 changes: 5 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ jobs:
runs-on: ubuntu-latest
outputs:
noir_versions: ${{ steps.get_versions.outputs.versions }}

steps:
- name: Checkout sources
id: get_versions
run: |
run: |
# gh returns the Noir releases in reverse chronological order so we keep all releases published after the minimum supported version.
VERSIONS=$(gh release list -R noir-lang/noir --exclude-pre-releases --json tagName -q 'map(.tagName) | index(env.MINIMUM_NOIR_VERSION) as $index | if $index then .[0:$index+1] else [env.MINIMUM_NOIR_VERSION] end')
echo "versions=$VERSIONS"
Expand Down Expand Up @@ -58,13 +59,11 @@ jobs:
- name: Install Nargo
uses: noir-lang/[email protected]
with:
toolchain: ${{ env.MINIMUM_NOIR_VERSION }}

toolchain: ${{env.MINIMUM_NOIR_VERSION}}
- name: Run formatter
run: nargo fmt --check


# This is a job which depends on all test jobs and reports the overall status.
# This is a job which depends on all test jobs and reports the overall status.
# This allows us to add/remove test jobs without having to update the required workflows.
tests-end:
name: Noir End
Expand All @@ -85,4 +84,4 @@ jobs:
fi
env:
# We treat any cancelled, skipped or failing jobs as a failure for the workflow as a whole.
FAIL: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}
FAIL: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
target
.vscode/launch.json
export/*
gates_report.json
1 change: 0 additions & 1 deletion export/test_add_BN.json

This file was deleted.

34 changes: 34 additions & 0 deletions scripts/build-gates-report.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -e

BACKEND=${BACKEND:-bb}

cd $(dirname "$0")/../

artifacts_path="./export"
artifacts=$(ls $artifacts_path)

echo "{\"programs\": [" > gates_report.json

# Bound for checking where to place last parentheses
NUM_ARTIFACTS=$(ls -1q "$artifacts_path" | wc -l)

ITER="1"
for artifact in $artifacts; do
ARTIFACT_NAME=$(basename "$artifact")

GATES_INFO=$($BACKEND gates -b "$artifacts_path/$artifact")
MAIN_FUNCTION_INFO=$(echo $GATES_INFO | jq -r '.functions[0] | .name = "main"')
echo "{\"package_name\": \"$ARTIFACT_NAME\", \"functions\": [$MAIN_FUNCTION_INFO]" >> gates_report.json

if (($ITER == $NUM_ARTIFACTS)); then
echo "}" >> gates_report.json
else
echo "}, " >> gates_report.json
fi

ITER=$(( $ITER + 1 ))
done

echo "]}" >> gates_report.json

90 changes: 90 additions & 0 deletions src/benchmarks/bignum_benchmarks.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use crate::utils::u60_representation::U60Repr;

use crate::bignum::BigNum;
use crate::bignum::BigNumTrait;

use crate::params::BigNumParams;
use crate::params::BigNumParamsGetter;

use crate::fields::bls12_381Fq::BLS12_381_Fq_Params;
use crate::fields::bn254Fq::BN254_Fq_Params;
use crate::fields::U256::U256Params;

// the types we will be benchmarking

type Fq = BigNum<3, 254, BN254_Fq_Params>;
type BN256 = BigNum<3, 257, U256Params>;
type BN381 = BigNum<4, 381, BLS12_381_Fq_Params>;

// macro magic to generate the benchmarks
fn bench_add<let N: u32, BN>(a: BN, b: BN)
where
BN: BigNumTrait,
{
let c = a + b;
}

fn bench_sub<let N: u32, BN>(a: BN, b: BN)
where
BN: BigNumTrait,
{
let c = a - b;
}

fn bench_mul<let N: u32, BN>(a: BN, b: BN)
Copy link
Member

Choose a reason for hiding this comment

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

I'm not clear on why we need these functions rather than putting the arithmetic directly inside the #[export] functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

for these, it doesn't matter much I guess, but I thought it might be a good framework for more complicated functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

wonder if this introduces unnecessary memory gates for bb though.

where
BN: BigNumTrait,
{
let c = a * b;
}

fn bench_div<let N: u32, BN>(a: BN, b: BN)
where
BN: BigNumTrait,
{
let c = a / b;
}

// type Fq
// type BN256
// type BN381
// type BN2048
#[export]
fn bench_add_Fq(a: Fq, b: Fq) {
bench_add::<3, Fq>(a, b);
}

#[export]
fn bench_sub_Fq(a: Fq, b: Fq) {
bench_sub::<3, Fq>(a, b);
}

#[export]
fn bench_mul_Fq(a: Fq, b: Fq) {
bench_mul::<3, Fq>(a, b);
}

#[export]
fn bench_div_Fq(a: Fq, b: Fq) {
bench_div::<3, Fq>(a, b);
}

#[export]
fn bench_add_BN256(a: BN256, b: BN256) {
bench_add::<3, BN256>(a, b);
}

#[export]
fn bench_sub_BN256(a: BN256, b: BN256) {
bench_sub::<3, BN256>(a, b);
}

#[export]
fn bench_mul_BN256(a: BN256, b: BN256) {
bench_mul::<3, BN256>(a, b);
}

#[export]
fn bench_div_BN256(a: BN256, b: BN256) {
bench_div::<3, BN256>(a, b);
}
1 change: 1 addition & 0 deletions src/benchmarks/mod.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod bignum_benchmarks;
8 changes: 7 additions & 1 deletion src/bignum.nr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::params::BigNumParamsGetter;
use crate::fns::{
constrained_ops::{
add, assert_is_not_equal, conditional_select, derive_from_seed, div, eq, from_field, mul,
neg, sub, udiv, udiv_mod, umod, validate_in_field, validate_in_range,
neg, sub, udiv, udiv_mod, umod, validate_in_field, validate_in_range, to_field
},
expressions::{__compute_quadratic_expression, evaluate_quadratic_expression},
serialization::{from_be_bytes, to_le_bytes},
Expand All @@ -31,6 +31,7 @@ pub trait BigNumTrait: Neg + Add + Sub + Mul + Div + Eq {
pub fn from_slice(limbs: [Field]) -> Self;
pub fn from_be_bytes<let NBytes: u32>(x: [u8; NBytes]) -> Self;
pub fn to_le_bytes<let NBytes: u32>(self) -> [u8; NBytes];
pub fn to_field(self) -> Field;

pub fn modulus() -> Self;
pub fn modulus_bits(self) -> u32;
Expand Down Expand Up @@ -137,6 +138,11 @@ where
Self { limbs: limbs.as_array() }
}

pub fn to_field(self) -> Field {
to_field::<_, MOD_BITS>(Params::get_params(), self.limbs)
}


fn from_be_bytes<let NBytes: u32>(x: [u8; NBytes]) -> Self {
Self { limbs: from_be_bytes::<_, MOD_BITS, _>(x) }
}
Expand Down
39 changes: 35 additions & 4 deletions src/fns/constrained_ops.nr
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,37 @@ use crate::params::BigNumParams as P;
* We use a hash function that can be modelled as a random oracle
* This function *should* produce an output that is a uniformly randomly distributed value modulo BigNum::modulus()
**/
pub(crate) fn to_field<let N: u32, let MOD_BITS: u32>(
params: P<N, MOD_BITS>,
limbs: [Field; N],
) -> Field {
let TWO_POW_120 = 0x1000000000000000000000000000000;
// let mut field: Field = 0;
if N > 2 {
// validate that the limbs is less than the modulus the grumpkin modulus
let mut grumpkin_modulus = [0; N];
grumpkin_modulus[0] = 0x33e84879b9709143e1f593f0000001;
grumpkin_modulus[1] = 0x4e72e131a029b85045b68181585d28;
grumpkin_modulus[2] = 0x3064;
validate_gt::<N, MOD_BITS>(grumpkin_modulus, limbs);
// validate that the limbs are in range
validate_in_range::<N, MOD_BITS>(limbs);
}
// validate the limbs sum up to the field value
let field_val = if N < 2 {
limbs[0]
} else if N == 2 {
validate_in_range::<N, MOD_BITS>(limbs);
limbs[0] + limbs[1] * TWO_POW_120
} else {
validate_in_range::<N, MOD_BITS>(limbs);
limbs[0] + limbs[1] * TWO_POW_120 + limbs[2] * TWO_POW_120 * TWO_POW_120
};
field_val
// assert that the conversion is possible, i.e. the bignum is less than grumpkin field modulus

}

pub(crate) fn from_field<let N: u32, let MOD_BITS: u32>(
params: P<N, MOD_BITS>,
field: Field,
Expand All @@ -52,18 +83,18 @@ pub(crate) fn from_field<let N: u32, let MOD_BITS: u32>(
grumpkin_modulus[0] = 0x33e84879b9709143e1f593f0000001;
grumpkin_modulus[1] = 0x4e72e131a029b85045b68181585d28;
grumpkin_modulus[2] = 0x3064;
validate_gt::<N, 254>(grumpkin_modulus, result);
validate_gt::<N, MOD_BITS>(grumpkin_modulus, result);
// validate that the limbs are in range
validate_in_range::<N, 254>(result);
validate_in_range::<N, MOD_BITS>(result);
}
// validate the limbs sum up to the field value
let field_val = if N < 2 {
result[0]
} else if N == 2 {
validate_in_range::<N, 254>(result);
validate_in_range::<N, MOD_BITS>(result);
result[0] + result[1] * TWO_POW_120
} else {
validate_in_range::<N, 254>(result);
validate_in_range::<N, MOD_BITS>(result);
result[0] + result[1] * TWO_POW_120 + result[2] * TWO_POW_120 * TWO_POW_120
};
assert(field_val == field);
Expand Down
3 changes: 3 additions & 0 deletions src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ pub use runtime_bignum::RuntimeBigNum;

// Tests
mod tests;

// Benchmarks
pub mod benchmarks;
50 changes: 50 additions & 0 deletions src/tests/bignum_test.nr
Original file line number Diff line number Diff line change
Expand Up @@ -816,3 +816,53 @@ fn test_from_field_3_digits() {
assert(result == expected);
}

#[test]
fn test_to_field_one() {
let field: Field = 1;
let bn = Fq::one();
let result = bn.to_field();
assert(result == field);
}

#[test]
fn test_to_field_one_digit() {
let field: Field = 1066513542066841864585910935480267774;
let bn = Fq { limbs: [0xcd672d695ef3129e4c40867a7173fe, 0x0, 0x0] };
let result = bn.to_field();
assert(result == field);
}

#[test]
fn test_to_field_two_digits() {
let field: Field = 697955470585821007263499235110798476786097877002667034107578965871052378;
let bn =
Fq { limbs: [0x5a10b956d41840745e0a9f6e34465a, 0x65209b74583b912262843211905e41, 0x0] };
let result = bn.to_field();
assert(result == field);
}

#[test]
fn test_to_field_three_digits() {
let field: Field = 2330301921655783950764183713945533646391233209687308929386184468126823563744;
let bn =
Fq { limbs: [0x862cf8ea69d6c70c9cc8d8871b41e0, 0xe7763528201566c2fc8d93973cf1b4, 0x526] };
let result = bn.to_field();
assert(result == field);
}

#[test(should_fail_with = "BigNum::validate_gt check fails")]
fn test_to_field_three_digits_overflow() {
let bn: Fq = BigNum {
limbs: [0x4e6405505a33bb9b9c0563df2bd59a, 0x48dbe03a9bb4865ba961e41ef9dded, 0x3a36],
};
let result = bn.to_field();
}

#[test(should_fail_with = "BigNum::validate_gt check fails")]
fn test_to_field_too_many_digits() {
let bn: BN381 = BN381 {
limbs: [0xea1742447ee9d92f9f18e1c80a481e, 0x3d89ad3d3ae85f3f482a08435c93ec, 0x1e9f, 0x1],
};
let result = bn.to_field();
}

Loading