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

Implement more robust memory pool lookup (#243) #335

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Quba1
Copy link

@Quba1 Quba1 commented Dec 2, 2024

Fixes #243

I have implemented a more robust way to find big enough memory pool for reserve/alloc. I think comments explain quite well what happens in the code.

Minimal example of the solution
pub fn find_mem_pool(pool: &[usize], size: usize) -> usize {
    let ind = pool
        .binary_search(&size)
        .or_else(|found_index| {
            if found_index < pool.len() {
                Ok(found_index)
            } else {
                Err(found_index)
            }
        })
        .unwrap_or_else(|_| panic!("Cannot find pool big enough to reserve {size} bytes"));

    ind
}

#[cfg(test)]
mod tests {
    use crate::find_mem_pool;

    #[test]
    fn partitioning() {
        let v = [1, 3, 3, 5, 7];

        assert_eq!(find_mem_pool(&v, 0), 0);
        assert_eq!(find_mem_pool(&v, 1), 0);
        assert_eq!(find_mem_pool(&v, 2), 1);
        assert_eq!(find_mem_pool(&v, 3), 2);
        assert_eq!(find_mem_pool(&v, 5), 3);
        assert_eq!(find_mem_pool(&v, 6), 4);
        assert_eq!(find_mem_pool(&v, 7), 4);

        // this panics
        find_mem_pool(&v, 8);
    }
}

I have also added a simple test to check if the code panics when too much memory is requested.

To avoid code repetition in reserve() and alloc() I've extracted the solution into a separate function. But because alloc and reserve require &mut Storage the finder function needs to be implemented for directly for pools (to have two mutable borrows for pools and storage). But as Vec<DynamicPool> is a foreign type I had to implement the function via trait, hence the addition of PoolFind trait.

I hope this PR helps you. If something needs improvements or corrections, please let me know.

let pool_ind = self
// This finds a (not necessarily first) pool that matches the size exactly
// or returns error with index where a matching element could be inserted while maintaining sorted order
.binary_search_by_key(&size, |p| p.max_alloc_size())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size won't be equal to any of the max_alloc_sizes, we should find a bucket where size >= min_slice_size && size <= max_alloc_size. We don't need binary search since we only have < 10 buckets, max 20. Having the trait is great though.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I didn't know about the size >= min_slice_size condition. But I also can't find min_slice_size defined anywhere.

Is the Vec<DynamicPools> sorted by min_slice_size too? Because then binary search would still be possible.

Binary search was what partition_point() was doing and this implementation only extends that technique. But with such a small array size I do agree that linear search will not impact the performance and will allow for the size >= min_slice_size condition - I'll change the implementation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hiya - give I wrote this I'll barge in - there is no min_slice_size, that's implicit by the fact that that should have been allocated in the previous pool. Stuff being sorted is therefore a condition anyway - and binary search seems fine? I agree it's a small performance win but... idk, seems clearer & faster. I just didn't quite handle the error condition of the search correctly.

The trait also seems like it could just be a struct? Doubt you'd want multiple possible implementations of this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case there's only one condition, then the most important consideration for using binary search is that if there are two pools with same max_alloc_size then it's not predictable which one will be chosen. The question is if that's an acceptable behaviour.

The trait also seems like it could just be a struct? Doubt you'd want multiple possible implementations of this.

Struct or newtype would require changing the type of pools in MemoryManagement and I didn't feel comfortable doing that. So I created the trait, which I admit is a little bit excessive, but necessary for borrow checker.

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

Successfully merging this pull request may close these issues.

MemoryManagement<Storage>::reserve() can panic with index out of bounds
3 participants