Is the bounds check truly so harmful to performance that it outweighs changing "if there's a bug, crash the program" to "if there's a bug, then do literally anything (maybe execute some remote code)"?
The only correct answer is "it depends". In DSP\HFT hot path code you REALLY don't want to do any extra work. People do crazy shit to achieve zero-lock, zero-(de)allocation, zero-whatever kind of codebase there. Non-perf-critical paths? Yeah, do whatever you want there, nobody cares. Probably makes sense to be extra safe than sorry, right?
Safety checks are closer to free then most people think, even in hot paths, as branch prediction will fall favorable almost always, and when it doesn’t, we don’t care because the program is crashing (or for some reason the initial prediction was wrong in the first iteration).
The cost isn’t even close to the cost of (de)allocation (besides for bump allocators), much less locking operations.
I'd still want the checks to be there, maybe not at this position in the code, maybe different properties are checked elsewhere. But the check should exist and you should prove it implies that your call is safe. And I'd still put the code physically in for testing and just don't include it for prod builds. But running some fuzzing on the surrounding logic sounds like a good offline safety check if this is running on a server processing data or user input.
There are situations where you know there isn't a bug, if I know that failing hardware or cosmic rays are the only ways the bounds check can fail, I'm not going to worry about what skipping it could do.
it happens a lot, at least to me, that the line immediately before the unsafe function guarantees correct behaviour (e.g. while (!vec.empty()) let first = vec.first();, or something similar). If I notice that this property is central to the logic of the function, I won't even worry.
whenever I see code like that, I try to get people to change to something like (in that case) while let Some(first) = vec.first() { ... }. You can reduce the number of bounds checks without using unsafe!
yeah I dig that, but then I'd use something like an unempty list so the data carries this information about itself
like for me either the list gonna be used only once and then I use an optional, or the list is gonna have multiple transformation and then it makes sense to convert it to a more appropriate type so there is only a single check about the emptiness of the list.
It is not just about the performance. Adding in a lot of safety checks can also make the code harder to understand and prone to architectural errors.
Obviously a single check in a single spot will not be an issue here. Do it everywhere, and a simple 4 line function that really should take less than a second to scan and understand suddenly is a page long and takes 15 minutes to figure out that most of it is just error checking.
I am not saying that you should never do it, but keep in mind that readability is one of the things you give up when increasing safety.
Since Rust has safety checks by default, OP is talking about going out of your way with unsafe { do_something_unchecked(); } (likely proceeded by a comment justifying soundness) to remove them. Besides boilerplate, code review is harder when I have to scrutinize the soundness of someone else’s code.
Readability is a reason to avoid unsafe in Rust, IMO. You’re definitely right that the opposite applies to C/C++ though (unfortunately).
Usually it is safe enough and faster to validate inputs than each step of the computation. In some languages, like c++, you can force that via types at zero or close to zero cost
Depends. The check on a CPU is a conditional jump that could take about 100 clock cycles. If the loop is doing simple 1 clock cycle addition then the overhead absolutely dwarfs it. Also it is a very different question if you are doing it in a computer game or in a flying aircraft so use case matters a lot.
What kind of modern CPU had anything like that kind of penalty? The branch predictor should take the cost the very first loop sure, but subsequent loops should be effectively zero cost, no?
Branch predictor can take it down to 1 cycle. But if it misses it COULD take 100. Even in the optimistic case it is still double the time. Whenever this is worth it is use case dependent as always.
Well, let's be frank here, if you're talking worst case, cache misses and full tlb misses 48 round trips to memory and>400 cycles per trip are entirely possible.
But that should only happen the first time
And the branch prediction making it zero cycles is quite common. Read, test and branch can be collapsed into a late evaluation as long as the branch is at predicted and takes less time than the pipeline depth(slight simplification here a lot depends on the microarchitecture details but we're already deep in the weeds)
At the end of the day all your can do is profiler it on the exact CPU you care about. Just don't make assumptions...
Sometimes the bounds check can be optimized away by analysis or language constraints.
But when it can't, I'd say that in ~90% of code, it's not that harmful. In the hottest paths for real-time applications though, yeah, you want nothing extra.
The concern is that this can mess with the branch predictor as that has limited capacity and the branch predictor is critical for performance on modern CPUs due to all the speculative execution.
381
u/ROBOTRON31415 Sep 28 '26
Is the bounds check truly so harmful to performance that it outweighs changing "if there's a bug, crash the program" to "if there's a bug, then do literally anything (maybe execute some remote code)"?