Skip to content

Commit

Permalink
feat!: vector optimizations (#39)
Browse files Browse the repository at this point in the history
Co-authored-by: grjte <[email protected]>
Co-authored-by: saleel <[email protected]>
Co-authored-by: Khashayar Barooti <[email protected]>
  • Loading branch information
4 people authored Feb 25, 2025
1 parent acef93b commit 453401f
Show file tree
Hide file tree
Showing 12 changed files with 3,735 additions and 1,165 deletions.
49 changes: 49 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Benchmarks

on:
push:
branches:
- main
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.3

- name: Install bb
run: |
npm install -g bbup
bbup -nv 1.0.0-beta.3
sudo apt install libc++-dev
- 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@dbe920a8dcc3370af4be4f702ca9cef29317bec1
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 }}
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

env:
CARGO_TERM_COLOR: always
MINIMUM_NOIR_VERSION: v0.36.0
MINIMUM_NOIR_VERSION: v1.0.0-beta.1

jobs:
noir-version-list:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
target
.DS_Store
.vscode
export
gates_report.json
2 changes: 1 addition & 1 deletion Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
name = "noir_base64"
type = "lib"
authors = [""]
compiler_version = ">=0.36.0"
compiler_version = ">=1.0.0"

[dependencies]
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ Takes an arbitrary byte array as input, encodes it in Base64 according to the al

```
// bytes: [u8; N]
let base64 = BASE64_ENCODER.encode(bytes);
let base64 = BASE64_ENCODER::encode(bytes);
```

### `fn decode`
Takes a utf-8 byte array that encodes a Base64 string and attempts to decoded it into bytes according to the provided configuration specifying the alphabet and padding rules.

```
// base64: [u8; N]
let bytes = BASE64_DECODER.decode(base64);
let bytes = BASE64_DECODER::decode(base64);
```

## Example usage
Expand All @@ -48,10 +48,10 @@ fn encode_and_decode() {
let input: str<88> = "The quick brown fox jumps over the lazy dog, while 42 ravens perch atop a rusty mailbox.";
let base64_encoded = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZywgd2hpbGUgNDIgcmF2ZW5zIHBlcmNoIGF0b3AgYSBydXN0eSBtYWlsYm94Lg==";
let encoded:[u8; 120] = noir_base64::BASE64_ENCODER.encode(input.as_bytes());
let encoded:[u8; 120] = noir_base64::BASE64_ENCODER::encode(input.as_bytes());
assert(encoded == base64_encoded.as_bytes());
let decoded: [u8; 88] = noir_base64::BASE64_DECODER.decode(encoded);
let decoded: [u8; 88] = noir_base64::BASE64_DECODER::decode(encoded);
assert(decoded == input.as_bytes());
}
```
Expand Down
33 changes: 33 additions & 0 deletions scripts/build-gates-report.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/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
54 changes: 54 additions & 0 deletions src/benchmarks/mod.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use crate::decoder::{Base64DecodeBE, Base64DecodeBENoPad};
use crate::encoder::{Base64EncodeBE, Base64EncodeBENoPad, Base64EncodeBEUrlSafe};

#[export]
fn bench_encode_610(input: [u8; 610]) -> [u8; 816] {
Base64EncodeBE::encode(input)
}

#[export]
fn bench_encode_610_url_safe(input: [u8; 610]) -> [u8; 814] {
Base64EncodeBEUrlSafe::encode(input)
}

#[export]
fn bench_encode_610_var(input: BoundedVec<u8, 610>) -> BoundedVec<u8, 816> {
Base64EncodeBE::encode_var(input)
}

#[export]
fn bench_encode_610_url_safe_var(input: BoundedVec<u8, 610>) -> BoundedVec<u8, 814> {
Base64EncodeBEUrlSafe::encode_var(input)
}

#[export]
fn bench_encode_610_no_pad(input: [u8; 610]) -> [u8; 814] {
Base64EncodeBENoPad::encode(input)
}

#[export]
fn bench_encode_610_var_no_pad(input: BoundedVec<u8, 610>) -> BoundedVec<u8, 814> {
Base64EncodeBENoPad::encode_var(input)
}

#[export]
fn bench_decode_610(input: [u8; 816]) -> [u8; 610] {
(Base64DecodeBE::decode(input))
}

#[export]
fn bench_decode_610_var(input: BoundedVec<u8, 816>) -> BoundedVec<u8, 610> {
let r = (Base64DecodeBE::decode_var(input));
r
}

#[export]
fn bench_decode_610_no_pad(input: [u8; 816]) -> [u8; 610] {
(Base64DecodeBENoPad::decode(input))
}

#[export]
fn bench_decode_610_var_no_pad(input: BoundedVec<u8, 816>) -> BoundedVec<u8, 610> {
let r = (Base64DecodeBENoPad::decode_var(input));
r
}
53 changes: 53 additions & 0 deletions src/boundary_check.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
unconstrained fn __boundary_check<let Range: u32>(limit: u32) -> [Field; Range] {
let mut r: [Field; Range] = [0; Range];
for i in limit..Range {
r[i] = 1;
}
r
}

/**
* @brief Return a size-Range array of values that describe whether an index `i` is in the range `0<=i<limit`
* @details When evaluating variable-length loops of size `limit`, it is neccessary to iterate over a maximum bound defined at compile-time
* Any constraints or evaluations that occur where `i >= limit` must be discarded.
* This is most efficiently performed by using predicate `Field` values, where `predicate[i] = 0` if `i < limit`, otherwise `predicate[i] = 1`.
* This method efficiently generates such predicate values more efficiently than querying whether `i <= limit` at every iteration.
* Gate cost is 3 * Range
**/
pub fn boundary_check<let Range: u32>(limit: u32) -> [Field; Range] {
let r = unsafe {
//@safety r contains claims about whether `r[i] >= limit`. the rest of this function checks this claim is correct
__boundary_check(limit)
};

let mut transition_index = 0;
// **
// We have an array of Field elements `r` such that:
// if i < limit, `r = 0`
// if i >= limit, `r = 1`
// We validate the predicate list `r` is correct by checking:
// 1. every r[i] element is 0 or 1
// 2. if r[i] = 1, r[i+1] must also be 1
// 3. if r[i] = 0 and r[i+1] = 1, then i == limit
// we check point 3 by tracking a `transition_index` variable, where
// transition_index += (1 - r[i]) * (r[i+1]) * i
// i.e. if r[i] == 0 and r[i+1] == 1, transition_index += i
// else transition_index += 0
// NOTE: total constraint cost is 3 gates per iteration
// **
if Range > 0 {
for i in 0..Range - 1 {
assert_eq(r[i] * r[i], r[i]);
assert_eq(r[i] * r[i + 1], r[i]);
let idx = (r[i + 1] * (1 - r[i])) * (i as Field + 1);
transition_index = transition_index + idx;
std::as_witness(transition_index);
}
assert_eq(r[Range - 1] * r[Range - 1], r[Range - 1]);
transition_index = transition_index + (1 - r[Range - 1]) * limit as Field;
assert(transition_index == limit as Field);
r
} else {
[0; Range]
}
}
Loading

0 comments on commit 453401f

Please sign in to comment.