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

Add support for allocation limits on arenas #160

Merged
merged 17 commits into from
Aug 5, 2022
Merged

Conversation

Eliasin
Copy link
Contributor

@Eliasin Eliasin commented Jun 2, 2022

Implements #135

Arenas can optionally keep track of an allocation_limit which is
considered when allocating new chunks to an arena.

The limit is only checked in alloc_layout_slow and new_chunk, which means it
should have a low performance overhead as the limit is only checked when trying to hit
the global allocator.

To further avoid performance concerns, ChunkFooter has been refactored to keep
track of the total allocated bytes, allowing Bump::allocated_bytes to avoid
performing a walk of the chunk list.

No new new methods were added, and limits must be set after the
creation of a Bump arena. Limits can be changed on the fly and are
only enforced when attempting to allocate new chunks.

No changes were made to the errors returned by operations like alloc
because of backwards compatibility.

I'm new to open source contributions and library development so sorry if there's anything obvious I missed.

Copy link
Owner

@fitzgen fitzgen left a comment

Choose a reason for hiding this comment

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

Thanks! A few comments below.

src/alloc.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
@Eliasin Eliasin force-pushed the main branch 3 times, most recently from ce26866 to 4e3a178 Compare June 9, 2022 01:10
@Eliasin Eliasin requested a review from fitzgen June 9, 2022 01:13
@Eliasin
Copy link
Contributor Author

Eliasin commented Jun 22, 2022

Hey @fitzgen, just checking in to see if you've had time to review the latest changes? No worries if you're busy though 😃

Copy link
Owner

@fitzgen fitzgen left a comment

Choose a reason for hiding this comment

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

Thanks! Somehow this slipped through my email.

I have a few small comments below, but this generally looks good, and after those things are addressed we should be able to merge this.

Thanks for being patient!

src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
tests/allocation_limit.rs Outdated Show resolved Hide resolved
tests/allocation_limit.rs Outdated Show resolved Hide resolved
@Eliasin Eliasin force-pushed the main branch 4 times, most recently from f3fb2b0 to 8efa890 Compare June 24, 2022 04:14
@Eliasin
Copy link
Contributor Author

Eliasin commented Jun 24, 2022

All the comments should be resolved with these commits 😄

@Eliasin Eliasin requested a review from fitzgen June 24, 2022 04:20
@Eliasin
Copy link
Contributor Author

Eliasin commented Jun 24, 2022

On an unrelated note, between the time I opened the PR and now I started project selection for my 4th year project in school and am looking at doing work exploring the applications of WASM modules for IoT app development and I noticed that you're a maintainer of wasmtime.

I'll hopefully be working with wasmtime and wit-bindgen for this so I might talk to you later on the bytecode alliance zulip 😄.

@Eliasin
Copy link
Contributor Author

Eliasin commented Jun 24, 2022

All the comments should be resolved with these commits 😄

I just have one concern regarding how allocation limits interact with the value for the minimum chunk size allocation. If a limit is set that is below DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER, all allocations will fail because no sizes will be considered that are below it.

Should set_allocation_limit become fallible to warn about this case? Or should a note be left in the documentation for allocation limits?

Useful documentation notes might have to effectively make the value of DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER public, but because of the details of this implementation, any reductions to DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER could be a breaking change anyway.

@fitzgen
Copy link
Owner

fitzgen commented Jun 27, 2022

I just have one concern regarding how allocation limits interact with the value for the minimum chunk size allocation. If a limit is set that is below DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER, all allocations will fail because no sizes will be considered that are below it.

Ah this is a good point.

What if we chain on the limit as a size option to new_sizes in this scenario inside alloc_layout_slow:

let sizes = ...;

let sizes = sizes
    .chain(match self.allocation_limit() {
        Some(limit) if limit < DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER
            && self.allocated_bytes() == 0 => Some(limit),
        _ => None,
    });

let new_footer = ...;

And then the existing filter_map and size checking in Bump::new will make this Just Work.

@Eliasin
Copy link
Contributor Author

Eliasin commented Jun 27, 2022

I just have one concern regarding how allocation limits interact with the value for the minimum chunk size allocation. If a limit is set that is below DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER, all allocations will fail because no sizes will be considered that are below it.

Ah this is a good point.

What if we chain on the limit as a size option to new_sizes in this scenario inside alloc_layout_slow:

let sizes = ...;

let sizes = sizes
    .chain(match self.allocation_limit() {
        Some(limit) if limit < DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER
            && self.allocated_bytes() == 0 => Some(limit),
        _ => None,
    });

let new_footer = ...;

And then the existing filter_map and size checking in Bump::new will make this Just Work.

This mostly works but my testing finds it interacts badly with the page strategy for small allocations and the addition of memory for the footer, breaking the quickcheck for allocation limits never being exceeded.

To make this work I think that either the size check needs to be moved back into new_chunk or the logic for deciding the final layout of the allocated chunk needs to be factored out so it can be used as the size check in alloc_layout_slow.

I think this was technically a problem before the above change and was just masked by how the rounding + overhead + footer size adjustment is not significant for larger limits.

This does have it's own problems though as if the check is just moved around the issue changes from happening when trying to allocate less than DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER to happening when trying to allocate less than some amount which would be rounded up + overhead to over the limit, meaning it might be unclear to the end user why an allocation failed even though there is technically room before hitting the limit.

One way to work around this is to stipulate that the limit can be exceeded by some margin that covers the adjustment that new_chunk makes to requests. From what I can see, the max that new_chunk can decide to allocate over the requested size is 0x1000 - 1 - OVERHEAD when rounding large allocations to a page boundary, this margin of error may be unacceptably large though.

@Eliasin
Copy link
Contributor Author

Eliasin commented Jun 29, 2022

@fitzgen Since I'm not sure if plain comments trigger notifications

@Eliasin
Copy link
Contributor Author

Eliasin commented Jun 30, 2022

I took a stab at fixing this problem by factoring out the logic for determining the final size of chunk allocations so that the code in alloc_layout_slow could determine the exact size of a new allocated chunk. This fixes breaking the limit because of the difference in allocation request size and the final size.

I also allowed the chunk size halving process to go below the min chunk size when dealing with small limits as just requesting a chunk with the limit value usually rounds up past the limit and ends up being rejected.

@Eliasin
Copy link
Contributor Author

Eliasin commented Jun 30, 2022

I'm realizing now that this change causes some memory details about the new chunks to be calculated twice in a lot of cases so I'll make a change to avoid that now.

@Eliasin
Copy link
Contributor Author

Eliasin commented Jun 30, 2022

I'm realizing now that this change causes some memory details about the new chunks to be calculated twice in a lot of cases so I'll make a change to avoid that now.

I've made the change to cache the new chunk memory details so that they are not calculated twice but this necessitated a change in new_chunk, marking it as unsafe because of it no longer can verify (without recalculating and defeating the point) the safety of the new chunk memory details passed to it.

I'm not sure if that's an acceptable change and I'm not a fan of the name I have NewChunkMemoryDetails but I wanted to get a solution out and see what your opinion is.

Comment on lines +1487 to +1508
let chunk_memory_details = iter::from_fn(|| {
let bypass_min_chunk_size_for_small_limits = match self.allocation_limit() {
Some(limit)
if layout.size() < limit
&& base_size >= layout.size()
&& limit < DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER
&& self.allocated_bytes() == 0 =>
{
true
}
_ => false,
};

if base_size >= min_new_chunk_size || bypass_min_chunk_size_for_small_limits {
let size = base_size;
base_size = base_size / 2;
Some(size)
Bump::new_chunk_memory_details(Some(size), layout)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure if rustc is smart enough to transform this into

if base_size >= min_new_chunk_size {
    // ...
} else {
  let bypass_min_chunk_size_for_small_limits = ...;
  if bypass_min_chunk_size_for_small_limits {
     let size = base_size;
     base_size = base_size / 2;
     Bump::new_chunk_memory_details(Some(size), layout)
  } else {
    None
  }
}

but I decided to hold off on any premature optimizations since I think this is much less readable

Copy link
Owner

@fitzgen fitzgen left a comment

Choose a reason for hiding this comment

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

Thanks! This looks good modulo a couple small things below.

src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Show resolved Hide resolved
tests/allocation_limit.rs Outdated Show resolved Hide resolved
@Eliasin Eliasin requested a review from fitzgen July 6, 2022 00:48
Copy link
Owner

@fitzgen fitzgen left a comment

Choose a reason for hiding this comment

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

Thanks! And thanks for your patience getting this one 100% over the finish line!

@fitzgen
Copy link
Owner

fitzgen commented Jul 7, 2022

https://github.com/fitzgen/bumpalo/runs/7239112120?check_suite_focus=true#step:5:126

Looks like abs_diff wasn't introduced until Rust 1.60, and our MSRV is 1.50. Mind open coding abs_diff here?

@Eliasin
Copy link
Contributor Author

Eliasin commented Jul 7, 2022

@fitzgen Should be good now, I ran the tests on 1.54.0

@fitzgen
Copy link
Owner

fitzgen commented Jul 11, 2022

Looks like CI is still failing:

https://github.com/fitzgen/bumpalo/runs/7286188380?check_suite_focus=true#step:5:342

failures:
---- oom_instead_of_bump_pointer_overflow stdout ----
Layout::from_size_align errored: invalid parameters to Layout::from_size_align
note: test did not panic as expected
error: test failed, to rerun pass '--test tests'
failures:
    oom_instead_of_bump_pointer_overflow

@Eliasin
Copy link
Contributor Author

Eliasin commented Jul 11, 2022

Looks like CI is still failing:

https://github.com/fitzgen/bumpalo/runs/7286188380?check_suite_focus=true#step:5:342

failures:
---- oom_instead_of_bump_pointer_overflow stdout ----
Layout::from_size_align errored: invalid parameters to Layout::from_size_align
note: test did not panic as expected
error: test failed, to rerun pass '--test tests'
failures:
    oom_instead_of_bump_pointer_overflow

@fitzgen I think this might be a nightly breakage as I ran this test on upstream main with nightly and experienced a failure

@Eliasin
Copy link
Contributor Author

Eliasin commented Jul 11, 2022

Looks like CI is still failing:
https://github.com/fitzgen/bumpalo/runs/7286188380?check_suite_focus=true#step:5:342

failures:
---- oom_instead_of_bump_pointer_overflow stdout ----
Layout::from_size_align errored: invalid parameters to Layout::from_size_align
note: test did not panic as expected
error: test failed, to rerun pass '--test tests'
failures:
    oom_instead_of_bump_pointer_overflow

@fitzgen I think this might be a nightly breakage as I ran this test on upstream main with nightly and experienced a failure

The test passes on this PR and upstream main on the nightly of July 10th 2022 but not July 11th 2022, which is today

@Eliasin
Copy link
Contributor Author

Eliasin commented Jul 11, 2022

Toying with the latest nightly it seems that Layout::size_from_align panics with a lot of values that stable won't with. Namely values of size near usize::MAX. I'm not sure if this constitutes a nightly regression or where to even report something like this though

@Eliasin
Copy link
Contributor Author

Eliasin commented Jul 11, 2022

I asked on the rust zulip and it seems like there is a new requirement for Layout::from_size_align that the size value must fit in an isize from this PR which the nightly docs now show so the test will have to be changed

@Eliasin
Copy link
Contributor Author

Eliasin commented Jul 11, 2022

I made an issue for the breakage with a tentative suggested change

@Eliasin
Copy link
Contributor Author

Eliasin commented Jul 26, 2022

Hey @fitzgen , just pinging in case this slipped by your email 😃

@fitzgen
Copy link
Owner

fitzgen commented Jul 27, 2022

I don't have time to look into the old tests at the moment, if you can get them passing and send a PR then I can review that.

@fitzgen
Copy link
Owner

fitzgen commented Aug 4, 2022

I think this should pass CI now if rebased

Eliasin and others added 17 commits August 4, 2022 17:09
Arenas can optionally keep track of an `allocation_limit` which is
considered when allocating new chunks to an arena.

No new `new` methods were added, and limits must be set after the
creation of a `Bump` arena. Limits can be changed on the fly and are
only enforced when attempting to allocate new chunks.

No changes were made to the errors returned by operations like `alloc`
because of backwards compatibility.
Clarify that allocation limits are per `Bump` arena

Co-authored-by: Nick Fitzgerald <[email protected]>
ChunkFooter now tracks the allocated bytes, reducing the operation of
retrieving the allocated bytes to a field lookup instead of a linear
walk of the chunk list.
To ensure that allocations do not exceed the allocation limit imposed on
a `Bump`, the logic for calculating the final size of an allocated chunk
was factored out into a separate function so that the limit enforcing
code higher up in the control flow could more strictly enforce the
allocation limit, as previously this code did not have visibility into
the final size of an allocation request.

To ensure that allocations in arenas with small limits could succeed,
`alloc_layout_slow` was changed to allow bypassing the minimum chunk
size for new chunks when trying to allocate in new arenas when the limit
was lower than the default minimum chunk size.
When allocating new chunks, the memory layout for new chunks was
calculated twice, once for the code checking if the new chunk would
violate allocation limits and once for the actual code allocating the
new chunk.

This change caches the calculated new chunk memory layout inbetween
these steps to avoid the extra calculation.

This change means that `new_chunk` must be marked `unsafe` as before
this change `new_chunk` could guarantee that the memory details of a
new chunk were safe (correct alignment, size, etc.), now that it is
not responsible for calculating these details, callers can cause
unsoundness through misuse of `new_chunk`.
@Eliasin
Copy link
Contributor Author

Eliasin commented Aug 5, 2022

I think this should pass CI now if rebased

Seems like it does!

@fitzgen fitzgen merged commit f551c37 into fitzgen:main Aug 5, 2022
@fitzgen
Copy link
Owner

fitzgen commented Aug 5, 2022

Thanks! And thanks for your patience!

@Eliasin
Copy link
Contributor Author

Eliasin commented Aug 5, 2022

I had a great time working on this PR, thanks for your work reviewing it and maintaining this crate! 😄

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.

2 participants