-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Large BTreeMap key or value types cause avoidable stack overflow #81444
Comments
That was for the key type, the value type has a little more leeway. With slightly modified code to avoid the missing
the limit for the value type in Rust 1.30, 1.32 and 1.33 is somewhere between 37,600 and 37,700. Since Rust 1.34, it has dropped to somewhere between 20,700 and 20,800. |
Not to the Rust 1.33 level. It merely increases the size limit from about 20,700 to about 26,600. |
This seems like a specific instance of the general "placement new" problem, #27779. Semantically, the values are created locally on the stack and then moved into the map node. Sometimes optimization could be smart enough to skip the local, but there's no guarantee of this, and debug builds are out of luck. The creation of the node is more directly concerning though, being a multiple of the key-value size. We have more control to avoid that ever hitting the stack if they would use |
…acrum Initialize BTree nodes directly in the heap We can avoid any stack-local nodes entirely by using `Box::new_uninit`, and since the nodes are mostly `MaybeUninit` fields, we only need a couple of actual writes before `assume_init`. This should help with the stack overflows in rust-lang#81444, and may also improve performance in general. r? `@Mark-Simulacrum` cc `@ssomers`
…k-Simulacrum BTree: no longer copy keys and values before dropping them When dropping BTreeMap or BTreeSet instances, keys-value pairs are up to now each copied and then dropped, at least according to source code. This is because the code for dropping and for iterators is shared. This PR postpones the treatment of doomed key-value pairs from the intermediate functions `deallocating_next`(`_back`) to the last minute, so the we can drop the keys and values in place. According to the library/alloc benchmarks, this does make a difference, (and a positive difference with an `#[inline]` on `drop_key_val`). It does not change anything for rust-lang#81444 though. r? `@Mark-Simulacrum`
I'm a bit confused. This code overflows the stack on stable, but not beta or nightly on the playground: use std::collections::BTreeMap;
fn main() {
let x: [u16; 32768 * 4] = [0; 32768 * 4];
let mut bm = BTreeMap::new();
bm.insert(0, x);
} Not overflowing on beta means that whatever fixes this will be in the next stable release, right? But I'm not seeing any activity on this issue since May of last year, so I'm confused. |
I don't actually see that behaviour on your playground example; instead, all release builds succeed and all debug builds overflow. Maybe playground assigns different platforms depending on karma. I do see it with my value example on my own Windows machine: maximum value size has increased from about Which includes the very much related #92962, but I'm stumped whether and how this would have reduced stack usage. Sure, the allocation on the heap is postponed a little, but why would the stack care? If the |
I don't know what to tell you except I'm finding this to be very reproducible. |
You're right, I must have bungled up the first time. I was also focusing on release builds but my command-line tests are debug too. Let's make sure of one thing: in release builds, in my example, the same nightly-2022-03-21 increased the value type size limit from about 150k to 350k. I put the same example as a test in frank's branch, e,g. #[test]
fn pain() {
let mut s = std::collections::BTreeMap::new();
s.insert((), [0u8; 500_000]);
} That confirms that it's really #92962 that somehow resolved the stack issue. Besides the question how, another mystery is that the numbers are considerably higher than 350k, whether the example is internal or external (built inside or outside the alloc module). I'm classifying that as if |
#92962 obviously made all the above examples special: they insert into a fresh, treeless map. If we let them insert a second time, into a then "genuine" map, then instead of improving matters, we have the original regression problem: something works fine in stable (playground) but fails on beta and nightly. |
I tried this code:
built with plain
rustc
on 64 bit Windows (it happens in release code too but not with this example).I expected to see the program finishing silently.
Instead, this happened:
This becomes sporadic if you decrease the size of the key and disappears below 19540 bytes (on my system).
On my Linux box, and on playground, the size can be increased to somewhere between 160_000 and 170_000 bytes before the stack overflows.
rustc --version --verbose
:This is not necessarily a bug, since there has to be some system-dependent limit. And it's not smart to inline big chunks of data as key or value, because usually about half of the key-value space allocated by BTreeMap remains unused. But the limit on Windows is much lower than I expected (and before #81494 can apparently easily be
liftedimproved by usingbox
instead ofBox::new
for the construction of btree nodes).The text was updated successfully, but these errors were encountered: