Arena backing for Vecs and arrays used within an iter

As someone who's currently obssessing over the perf of my toy utility, I learnt about the miracle of pre-allocation to reduce run-time allocations, syscalls, etc. Got a 3x reduction in execution time by just creating Vecs with their maximum possible (in my use case) size/capacity, rather than creating empty and letting them expand.

So, having learnt of bump allocation or just pre-allocating in general, I'm trying to understand how I can use elsa (elsa - Rust) in my fn main(), such that the Vecs and arrays created in each of my iterator's iteration (next()) is backed by this FrozenVec or some pre-allocated block of memory.

Not looking to hold the whole input file in memory, as I have an iterator that can read things in serially, then parallellize the crunching.
Each Item returned by the iterator is a Vec of a single type (u8, u16, whatever), and I only need a reference to that Vec, as I won't be modifiying the content, just reading and parsing.

To be honest, this is an advanced topic for me w.r.t understanding all examples.
So any guidance on how to think and go about this will be really helpful.

Thanks in advance.

Have you tried https://docs.rs/bumpalo?

Hi,

Thanks for responding.
That's another crate I've been looking at.
cargo add / cargo sync was behaving weird earlier today, so couldn't get this added to my project.

So... I'd create a new bump, then put that in my iter's definition struct, then use it within fn next() for future with_capacity_in requests?

No, because items returned from the iterator cannot borrow from the iterator. The iterator must borrow the Bump, not own it.

Ah yes, sorry that's what I should have said... as that's what's shown in the example in the link.

EDIT: Had to add this to Cargo.toml

bumpalo = { version = "3", features = ["collections", "allocator-api2"]}
allocator-api2 = "0"

BTW @cuviper, this is why I didn't know if bumpalo would be usable for me.

"The Bump is !Sync, which makes it hard to use in certain situations around threads ‒ for example in rayon."

Or.. is it only an issue in rayon, not as a user of rayon?

Great, now it complains about things that are way out of my depth :frowning:

error[E0106]: missing lifetime specifier
  --> src/impls.rs:49:31
   |
49 |     type Item = io::Result<Vec<u8>>;
   |                               ^ expected named lifetime parameter
   |
help: consider introducing a named lifetime parameter
   |
49 |     type Item<'a> = io::Result<Vec<'a, u8>>;
   |              ++++                  +++


error[E0308]: mismatched types
    --> src/impls.rs:67:21
     |
67   |         data.append(&mut len_bytes);
     |              ------ ^^^^^^^^^^^^^^ expected `&mut Vec<'_, _>`, found `&mut [u8; 2]`
     |              |
     |              arguments to this method are incorrect
     |
     = note: expected mutable reference `&mut bumpalo::collections::Vec<'_, _>`
                found mutable reference `&mut [u8; 2]`


error[E0308]: mismatched types
   --> src/impls.rs:69:71
    |
69  |         if let Err(err) = self.source.by_ref().take(len - 2).read_to_end(&mut data) {
    |                                                           ----------- ^^^^^^^^^ expected `&mut Vec<u8>`, found `&mut Vec<'_, _>`
    |                                                           |
    |                                                           arguments to this method are incorrect
    |
    = note: `bumpalo::collections::Vec<'_, _>` and `std::vec::Vec<u8>` have similar names, but are actually distinct types
note: `bumpalo::collections::Vec<'_, _>` is defined in crate `bumpalo`



error[E0599]: the method `par_bridge` exists for struct `RecordIter<File>`, but its trait bounds were not satisfied
   --> src/main.rs:31:10
    |
30  |       let output: Vec<_> = file_struct
    |  __________________________-
31  | |         .par_bridge()
    | |         -^^^^^^^^^^ method cannot be called on `RecordIter<File>` due to unsatisfied trait bounds
    | |_________|
    | 
    |
   ::: src/impls.rs:43:1
    |
43  |   pub struct RecordIter<R> {
    |   ------------------------
    |   |
    |   method `par_bridge` not found for this struct
    |   doesn't satisfy `_: ParallelBridge`
    |

It should be possible to combine arena allocators with the thread_local crate (e.g. using the type ThreadLocal<Bump>), which will result in one separate arena per thread, but that’s just some extra chunking :grin:

Or as the next line suggests:

The bumpalo-herd crate provides a pool of Bump allocators for use in such situations.

There's nothing in rayon that would be affected, nor anything particularly weird there, just that it's an example of a threaded environment where you have to care about Send and Sync.

I wonder if bumpalo-herd has any advantage over using ThreadLocal<Bump>.

@cuviper I did see bumpalo-herd, but to be honest, although I understand all this conceptually, I'm currently struggling to figure out how to implement this in a case like this Rust Playground

... which I call in main like so:

    let file_struct = RecordIter { line };

    let output: Vec<_> = file_struct
        .par_bridge()
        .map(parse)
        .filter(|x| x.contains("some.string"))
        .collect();

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.