Skip to content

Commit

Permalink
feat(perf): optimize FieldElement::num_bits
Browse files Browse the repository at this point in the history
  • Loading branch information
asterite committed Jan 22, 2025
1 parent 02056d6 commit d34182e
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 20 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions acvm-repo/acir_field/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ serde.workspace = true
ark-bn254.workspace = true
ark-bls12-381 = { workspace = true, optional = true }
ark-ff.workspace = true
ark-std.workspace = true

cfg-if.workspace = true

Expand Down
70 changes: 50 additions & 20 deletions acvm-repo/acir_field/src/field_element.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use ark_ff::PrimeField;
use ark_ff::Zero;
use ark_std::io::Write;
use num_bigint::BigUint;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
Expand Down Expand Up @@ -195,26 +196,9 @@ impl<F: PrimeField> AcirField for FieldElement<F> {

/// This is the number of bits required to represent this specific field element
fn num_bits(&self) -> u32 {
let bytes = self.to_be_bytes();

// Iterate through the byte decomposition and pop off all leading zeroes
let mut iter = bytes.iter().skip_while(|x| (**x) == 0);

// The first non-zero byte in the decomposition may have some leading zero-bits.
let Some(head_byte) = iter.next() else {
// If we don't have a non-zero byte then the field element is zero,
// which we consider to require a single bit to represent.
return 1;
};
let num_bits_for_head_byte = head_byte.ilog2();

// Each remaining byte in the byte decomposition requires 8 bits.
//
// Note: count will panic if it goes over usize::MAX.
// This may not be suitable for devices whose usize < u16
let tail_length = iter.count() as u32;

8 * tail_length + num_bits_for_head_byte + 1
let mut bit_counter = BitCounter::default();
self.0.serialize_uncompressed(&mut bit_counter).unwrap();
bit_counter.bits()
}

fn to_u128(self) -> u128 {
Expand Down Expand Up @@ -354,6 +338,52 @@ impl<F: PrimeField> SubAssign for FieldElement<F> {
}
}

#[derive(Default, Debug)]
struct BitCounter {
/// Total number of non-zero bytes we found.
count: usize,
/// Total bytes we found.
total: usize,
/// The last non-zero byte we found.
head_byte: u8,
}

impl BitCounter {
fn bits(&self) -> u32 {
// If we don't have a non-zero byte then the field element is zero,
// which we consider to require a single bit to represent.
if self.count == 0 {
return 1;
}

let num_bits_for_head_byte = self.head_byte.ilog2();

// Each remaining byte in the byte decomposition requires 8 bits.
//
// Note: count will panic if it goes over usize::MAX.
// This may not be suitable for devices whose usize < u16
let tail_length = (self.count - 1) as u32;
8 * tail_length + num_bits_for_head_byte + 1
}
}

impl Write for BitCounter {
fn write(&mut self, buf: &[u8]) -> ark_std::io::Result<usize> {
for byte in buf {
self.total += 1;
if *byte != 0 {
self.count = self.total;
self.head_byte = *byte;
}
}
Ok(buf.len())
}

fn flush(&mut self) -> ark_std::io::Result<()> {
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::{AcirField, FieldElement};
Expand Down

0 comments on commit d34182e

Please sign in to comment.