num-bigint icon indicating copy to clipboard operation
num-bigint copied to clipboard

Feature request: zero-copy constructor?

Open Wandalen opened this issue 3 years ago • 7 comments

Hi. I have input data in a raw buffer. I am doing this:

let data : &Vec< u8 >;
let data_big = BigUint::from_bytes_le( &data );

I would like to achieve zero-copy solution. Is that possible/planned with the wonderful library?

Wandalen avatar Sep 18 '21 07:09 Wandalen

Seems that's something the library can't do at the moment. What about implementing it? I can open a PR. In theory not that hard. If data is little-ended and it is aligned with usize then it's enough to reinterpret the original buffer without copying it. Implementing zero-copy constructor would make num complying with Rust's: "Fast, Reliable, Productive . Pick Three ".

Wandalen avatar Sep 19 '21 14:09 Wandalen

It's not possible to borrow from an existing buffer because there's no lifetime parameter in the type (like BigUint<'a>).

In theory not that hard. If data is little-ended and it is aligned with usize then it's enough to reinterpret the original buffer without copying it.

If we instead consumed a Vec<u8> buffer, one could imagine reinterpreting that as Vec<BigDigit>, but the problem there is that the layout must also match how the buffer was allocated when it is later passed to realloc or dealloc.

Implementing zero-copy constructor would make num complying with Rust's: "Fast, Reliable, Productive . Pick Three ".

Performance is a goal, but that doesn't mean it must come at all costs.

cuviper avatar Sep 21 '21 00:09 cuviper

Hi, @cuviper Hm.. Are you saying it's not possible? What about borrowing data from Vec? BigUint does not have to own its data. Also, it's possible to introduce alternative BigUint which does not own its data, if it's assumed BigUint does own. Another alternative is the functional approach: instead of creating an object, static functions could be exposed accepting vector as the first argument.

I didn't think a lot about alternatives. Please treat them as a product of brainstorm activity rather than well thought out plan. Nevertheless, a theoretical solution definitely exists.

Wandalen avatar Sep 21 '21 05:09 Wandalen

It's fundamental to Rust that a type can't borrow data without expressing that borrowed lifetime. We could add something like BigUintRef<'a> perhaps, but that's a lot of API we'd need to mirror to the new type. Frankly, I think it's probably rare that the constructor is a performance bottleneck in the first place.

Another alternative is the functional approach: instead of creating an object, static functions could be exposed accepting vector as the first argument.

I'm not sure what you're suggesting here -- could you sketch out that API?

cuviper avatar Sep 22 '21 01:09 cuviper

I was looking for an API like this today. What I had in mind was something like this where T can be anything that can be borrowed as a slice of digits:

use std::borrow::BorrowMut;

struct BigUint<T> {
    data: T,
}

impl <T: BorrowMut<[u64]>> BigUint<T> {
    pub fn new(data: T) -> Self {
        Self { data }
    }
    
    /// Returns the number of least-significant bits that are zero,
    /// or `None` if the entire number is zero.
    pub fn trailing_zeros(&self) -> Option<u64> {
        let data = self.data.borrow();
        
        let i = data.iter().position(|&digit| digit != 0)?;
        let zeros: u64 = data[i].trailing_zeros().into();
        Some((i as u64) * 64 + zeros)
    }
}


fn main() {
    let mut digits: Vec<u64> = vec![0, 1, 0];
    
    // First operate on a mut ref
    let bigint = BigUint::new(&mut *digits);
    println!("{:?}", bigint.trailing_zeros());
    
    // Then consume the whole thing
    let bigint = BigUint::new(digits);
    println!("{:?}", bigint.trailing_zeros());
}

petrosagg avatar Oct 04 '21 09:10 petrosagg

By the way

  let vec1 = vec![ 0; 15 ];
  println!( "{:p}", &vec1[ .. ] );
  let mut buff = Cursor::new( vec1 );
  let vec2 = &buff.into_inner();
  println!( "{:p}", &vec2[ .. ] );

That code does not allocate unnecessary memory. Maybe num-bigint can borrow the logic and apply it?

Wandalen avatar Nov 02 '21 16:11 Wandalen

Might be useful: https://github.com/sdroege/byte-slice-cast/blob/master/src/lib.rs

Wandalen avatar Nov 02 '21 21:11 Wandalen