Skip to content

Commit 453401f

Browse files
zac-williamsongrjtesaleelKhashayar Barooti
authored
feat!: vector optimizations (#39)
Co-authored-by: grjte <[email protected]> Co-authored-by: saleel <[email protected]> Co-authored-by: Khashayar Barooti <[email protected]>
1 parent acef93b commit 453401f

File tree

12 files changed

+3735
-1165
lines changed

12 files changed

+3735
-1165
lines changed

.github/workflows/benchmark.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Benchmarks
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
jobs:
10+
test:
11+
name: Benchmark library
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout sources
15+
uses: actions/checkout@v4
16+
17+
- name: Install Nargo
18+
uses: noir-lang/[email protected]
19+
with:
20+
toolchain: 1.0.0-beta.3
21+
22+
- name: Install bb
23+
run: |
24+
npm install -g bbup
25+
bbup -nv 1.0.0-beta.3
26+
sudo apt install libc++-dev
27+
28+
- name: Build Noir benchmark programs
29+
run: nargo export
30+
31+
- name: Generate gates report
32+
run: ./scripts/build-gates-report.sh
33+
env:
34+
BACKEND: /home/runner/.bb/bb
35+
36+
- name: Compare gates reports
37+
id: gates_diff
38+
uses: noir-lang/noir-gates-diff@dbe920a8dcc3370af4be4f702ca9cef29317bec1
39+
with:
40+
report: gates_report.json
41+
summaryQuantile: 0.9 # only display the 10% most significant circuit size diffs in the summary (defaults to 20%)
42+
43+
- name: Add gates diff to sticky comment
44+
if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target'
45+
uses: marocchino/sticky-pull-request-comment@v2
46+
with:
47+
# delete the comment in case changes no longer impact circuit sizes
48+
delete: ${{ !steps.gates_diff.outputs.markdown }}
49+
message: ${{ steps.gates_diff.outputs.markdown }}

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88

99
env:
1010
CARGO_TERM_COLOR: always
11-
MINIMUM_NOIR_VERSION: v0.36.0
11+
MINIMUM_NOIR_VERSION: v1.0.0-beta.1
1212

1313
jobs:
1414
noir-version-list:

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
target
22
.DS_Store
33
.vscode
4+
export
5+
gates_report.json

Nargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
name = "noir_base64"
33
type = "lib"
44
authors = [""]
5-
compiler_version = ">=0.36.0"
5+
compiler_version = ">=1.0.0"
66

77
[dependencies]

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ Takes an arbitrary byte array as input, encodes it in Base64 according to the al
2929

3030
```
3131
// bytes: [u8; N]
32-
let base64 = BASE64_ENCODER.encode(bytes);
32+
let base64 = BASE64_ENCODER::encode(bytes);
3333
```
3434

3535
### `fn decode`
3636
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.
3737

3838
```
3939
// base64: [u8; N]
40-
let bytes = BASE64_DECODER.decode(base64);
40+
let bytes = BASE64_DECODER::decode(base64);
4141
```
4242

4343
## Example usage
@@ -48,10 +48,10 @@ fn encode_and_decode() {
4848
let input: str<88> = "The quick brown fox jumps over the lazy dog, while 42 ravens perch atop a rusty mailbox.";
4949
let base64_encoded = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZywgd2hpbGUgNDIgcmF2ZW5zIHBlcmNoIGF0b3AgYSBydXN0eSBtYWlsYm94Lg==";
5050
51-
let encoded:[u8; 120] = noir_base64::BASE64_ENCODER.encode(input.as_bytes());
51+
let encoded:[u8; 120] = noir_base64::BASE64_ENCODER::encode(input.as_bytes());
5252
assert(encoded == base64_encoded.as_bytes());
5353
54-
let decoded: [u8; 88] = noir_base64::BASE64_DECODER.decode(encoded);
54+
let decoded: [u8; 88] = noir_base64::BASE64_DECODER::decode(encoded);
5555
assert(decoded == input.as_bytes());
5656
}
5757
```

scripts/build-gates-report.sh

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
4+
BACKEND=${BACKEND:-bb}
5+
6+
cd $(dirname "$0")/../
7+
8+
artifacts_path="./export"
9+
artifacts=$(ls $artifacts_path)
10+
11+
echo "{\"programs\": [" > gates_report.json
12+
13+
# Bound for checking where to place last parentheses
14+
NUM_ARTIFACTS=$(ls -1q "$artifacts_path" | wc -l)
15+
16+
ITER="1"
17+
for artifact in $artifacts; do
18+
ARTIFACT_NAME=$(basename "$artifact")
19+
20+
GATES_INFO=$($BACKEND gates -b "$artifacts_path/$artifact")
21+
MAIN_FUNCTION_INFO=$(echo $GATES_INFO | jq -r '.functions[0] | .name = "main"')
22+
echo "{\"package_name\": \"$ARTIFACT_NAME\", \"functions\": [$MAIN_FUNCTION_INFO]" >> gates_report.json
23+
24+
if (($ITER == $NUM_ARTIFACTS)); then
25+
echo "}" >> gates_report.json
26+
else
27+
echo "}, " >> gates_report.json
28+
fi
29+
30+
ITER=$(( $ITER + 1 ))
31+
done
32+
33+
echo "]}" >> gates_report.json

src/benchmarks/mod.nr

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use crate::decoder::{Base64DecodeBE, Base64DecodeBENoPad};
2+
use crate::encoder::{Base64EncodeBE, Base64EncodeBENoPad, Base64EncodeBEUrlSafe};
3+
4+
#[export]
5+
fn bench_encode_610(input: [u8; 610]) -> [u8; 816] {
6+
Base64EncodeBE::encode(input)
7+
}
8+
9+
#[export]
10+
fn bench_encode_610_url_safe(input: [u8; 610]) -> [u8; 814] {
11+
Base64EncodeBEUrlSafe::encode(input)
12+
}
13+
14+
#[export]
15+
fn bench_encode_610_var(input: BoundedVec<u8, 610>) -> BoundedVec<u8, 816> {
16+
Base64EncodeBE::encode_var(input)
17+
}
18+
19+
#[export]
20+
fn bench_encode_610_url_safe_var(input: BoundedVec<u8, 610>) -> BoundedVec<u8, 814> {
21+
Base64EncodeBEUrlSafe::encode_var(input)
22+
}
23+
24+
#[export]
25+
fn bench_encode_610_no_pad(input: [u8; 610]) -> [u8; 814] {
26+
Base64EncodeBENoPad::encode(input)
27+
}
28+
29+
#[export]
30+
fn bench_encode_610_var_no_pad(input: BoundedVec<u8, 610>) -> BoundedVec<u8, 814> {
31+
Base64EncodeBENoPad::encode_var(input)
32+
}
33+
34+
#[export]
35+
fn bench_decode_610(input: [u8; 816]) -> [u8; 610] {
36+
(Base64DecodeBE::decode(input))
37+
}
38+
39+
#[export]
40+
fn bench_decode_610_var(input: BoundedVec<u8, 816>) -> BoundedVec<u8, 610> {
41+
let r = (Base64DecodeBE::decode_var(input));
42+
r
43+
}
44+
45+
#[export]
46+
fn bench_decode_610_no_pad(input: [u8; 816]) -> [u8; 610] {
47+
(Base64DecodeBENoPad::decode(input))
48+
}
49+
50+
#[export]
51+
fn bench_decode_610_var_no_pad(input: BoundedVec<u8, 816>) -> BoundedVec<u8, 610> {
52+
let r = (Base64DecodeBENoPad::decode_var(input));
53+
r
54+
}

src/boundary_check.nr

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
unconstrained fn __boundary_check<let Range: u32>(limit: u32) -> [Field; Range] {
2+
let mut r: [Field; Range] = [0; Range];
3+
for i in limit..Range {
4+
r[i] = 1;
5+
}
6+
r
7+
}
8+
9+
/**
10+
* @brief Return a size-Range array of values that describe whether an index `i` is in the range `0<=i<limit`
11+
* @details When evaluating variable-length loops of size `limit`, it is neccessary to iterate over a maximum bound defined at compile-time
12+
* Any constraints or evaluations that occur where `i >= limit` must be discarded.
13+
* This is most efficiently performed by using predicate `Field` values, where `predicate[i] = 0` if `i < limit`, otherwise `predicate[i] = 1`.
14+
* This method efficiently generates such predicate values more efficiently than querying whether `i <= limit` at every iteration.
15+
* Gate cost is 3 * Range
16+
**/
17+
pub fn boundary_check<let Range: u32>(limit: u32) -> [Field; Range] {
18+
let r = unsafe {
19+
//@safety r contains claims about whether `r[i] >= limit`. the rest of this function checks this claim is correct
20+
__boundary_check(limit)
21+
};
22+
23+
let mut transition_index = 0;
24+
// **
25+
// We have an array of Field elements `r` such that:
26+
// if i < limit, `r = 0`
27+
// if i >= limit, `r = 1`
28+
// We validate the predicate list `r` is correct by checking:
29+
// 1. every r[i] element is 0 or 1
30+
// 2. if r[i] = 1, r[i+1] must also be 1
31+
// 3. if r[i] = 0 and r[i+1] = 1, then i == limit
32+
// we check point 3 by tracking a `transition_index` variable, where
33+
// transition_index += (1 - r[i]) * (r[i+1]) * i
34+
// i.e. if r[i] == 0 and r[i+1] == 1, transition_index += i
35+
// else transition_index += 0
36+
// NOTE: total constraint cost is 3 gates per iteration
37+
// **
38+
if Range > 0 {
39+
for i in 0..Range - 1 {
40+
assert_eq(r[i] * r[i], r[i]);
41+
assert_eq(r[i] * r[i + 1], r[i]);
42+
let idx = (r[i + 1] * (1 - r[i])) * (i as Field + 1);
43+
transition_index = transition_index + idx;
44+
std::as_witness(transition_index);
45+
}
46+
assert_eq(r[Range - 1] * r[Range - 1], r[Range - 1]);
47+
transition_index = transition_index + (1 - r[Range - 1]) * limit as Field;
48+
assert(transition_index == limit as Field);
49+
r
50+
} else {
51+
[0; Range]
52+
}
53+
}

0 commit comments

Comments
 (0)