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

Feature request: zero-copy constructor? #221

Open
Wandalen opened this issue Sep 18, 2021 · 7 comments
Open

Feature request: zero-copy constructor? #221

Wandalen opened this issue Sep 18, 2021 · 7 comments

Comments

@Wandalen
Copy link

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
Copy link
Author

Wandalen commented Sep 19, 2021

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 Wandalen changed the title Feature request: zero-copy? Feature request: zero-copy constructor? Sep 19, 2021
@cuviper
Copy link
Member

cuviper commented Sep 21, 2021

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.

@Wandalen
Copy link
Author

Wandalen commented Sep 21, 2021

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.

@cuviper
Copy link
Member

cuviper commented Sep 22, 2021

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?

@petrosagg
Copy link

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());
}

@Wandalen
Copy link
Author

Wandalen commented Nov 2, 2021

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
Copy link
Author

Wandalen commented Nov 2, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants