[HN Gopher] How much does Rust's bounds checking cost?
       ___________________________________________________________________
        
       How much does Rust's bounds checking cost?
        
       Author : glittershark
       Score  : 118 points
       Date   : 2022-11-30 18:38 UTC (4 hours ago)
        
 (HTM) web link (blog.readyset.io)
 (TXT) w3m dump (blog.readyset.io)
        
       | killingtime74 wrote:
       | Can someone smarter than me enlighten me when you would consider
       | disabling bounds checking for performance? In ways the compiler
       | is not already doing so? The article starts with a bug that would
       | have been prevented by bounds checking. It's like talking about
       | how much faster a car would go if it didn't have to carry the
       | extra weight of brakes.
        
         | pornel wrote:
         | In tight numeric code that benefits from autovectorization.
         | 
         | Bound checks prevent some optimizations, since they're a branch
         | with a significant side effect that the compiler must preserve.
        
         | masklinn wrote:
         | > It's like talking about how much faster a car would go if it
         | didn't have to carry the extra weight of brakes.
         | 
         | And there's folks who do exactly that.
        
         | returningfory2 wrote:
         | I think the point of the article is the other way around: when
         | starting from a language like C that doesn't have bound
         | checking, moving to Rust will involve adding bounds checks and
         | then an argument will be made that this will regress
         | performance. So to test that hypothesis you start with the safe
         | Rust code, and then remove the bounds check to emulate what the
         | C code might be like. If, as in the article, you find that
         | performance is not really affected, then it makes a C-to-Rust
         | migration argument more compelling.
        
         | pitaj wrote:
         | Sometimes the programmer can prove that bounds checks are
         | unnecessary in a certain situation, but the compiler can't
         | prove that itself, and the programmer can't communicate that
         | proof to the compiler. Bounds checks _can_ result in lost
         | performance in some cases (very tight loops), so unsafe
         | facilities exist as a workaround (like `get_unchecked`).
        
         | heleninboodler wrote:
         | I don't really see this as a person who _wants_ to turn off the
         | bounds checking for any real reason, but as someone who just
         | wants to have an idea of what the cost of that bounds checking
         | is.
        
         | constantcrying wrote:
         | >Can someone smarter than me enlighten me when you would
         | consider disabling bounds checking for performance?
         | 
         | Because it is faster. Worst case you are triggering a branch
         | miss, which is quite expensive.
         | 
         | >It's like talking about how much faster a car would go if it
         | didn't have to carry the extra weight of brakes.
         | 
         | So? Every wasted CPU cycle costs money and energy. Especially
         | for very high performance applications these costs can be very
         | high. Not every car needs brakes, if it doesn't need to stop by
         | itself and crashing hurts nobody they are just waste.
        
         | jackmott42 wrote:
         | In Rust you can usually use iterators to avoid bounds checks.
         | This is idiomatic and the fast way to do things, so usually
         | when using Rust you don't worry about this at all.
         | 
         | But, occasionally, you have some loop that can't be done with
         | an iterator, AND its part of a tight loop where removing a
         | single conditional jump matters to you. When that matters it is
         | a real easy thing to use an unsafe block to index into the
         | array without the check. The good news is then at least in your
         | 1 million line program, the unsafe parts are only a few lines
         | that you are responsible for being sure are correct.
        
         | emn13 wrote:
         | Sometimes the extra speed can be relevant. Knowing what the
         | upside _can_ be can help inform the choice whether it's worth
         | it.
         | 
         | Secondly, even assuming you want runtime bounds checking
         | everywhere, then this is still a useful analysis because if you
         | learn that bounds-checking has no relevant overhead - great! No
         | need to look at that if you need to optimize. But if you learn
         | that it _does_ have an overhead, then you have the knowledge to
         | guide your next choices - is it enough to be worth spending any
         | attention on? If you want the safety, perhaps there's specific
         | code paths you can restructure to make it easier for the
         | compiler to elide the checks, or the branch predictor to make
         | em smaller? Perhaps you can do fewer indexing operations
         | altogether? Or perhaps there's some very specific small hot-
         | path you feel you can make an exception for; use bounds-
         | checking 99% of the time, but not in _that_ spot? All of these
         | avenues are only worth even exploring if there 's anything to
         | gain here in the first place.
         | 
         | And then there's the simple fact that having a good intuition
         | for where machines spend their time makes it easier to write
         | performant code right off the bat, and it makes it easier to
         | guess where to look first when you're trying to eek out better
         | perf.
         | 
         | Even if you like or even need a technique like bounds checking,
         | knowing the typical overheads can be useful.
        
         | d265f278 wrote:
         | I've seen bounds checks being compiled to a single integer
         | comparison followed by a jump (on x86 at least). This should
         | have a negligible performance impact for most programs running
         | on a modern, parallel CPU. However, for highly optimized
         | programs that constantly saturate all processor instruction
         | ports, bounds checks might of course become a bottleneck.
         | 
         | I think the most preferable solution (although not always
         | possible) would be to use iterators as much as possible. This
         | would allow rustc to "know" the entire range of possible
         | indexes used at runtime, which makes runtime bounds checking
         | redundant.
         | 
         | Some old benchmarks here: https://parallel-rust-
         | cpp.github.io/v0.html#rustc
        
         | dathinab wrote:
         | In some very hot code (most times loops with math stuff were it
         | prevents some optimizations and/or the compiler fails to
         | eliminate it) it can lead to relevant performance improvements.
         | 
         | Because of this you might find some rust code which opts out of
         | bounds check for such code using unsafe code.
         | 
         | But this code tends to be fairly limited and often encapsulated
         | into libraries.
         | 
         | So I agree that for most code doing so is just plain stupid. In
         | turn I believe doing it on a program or compilation unit level
         | (instead of a case by case basis) is (nearly) always a bad
         | idea.
        
       | moloch-hai wrote:
       | The instructions generated make a big difference. Modern
       | processor specifications commonly quote how many instructions of
       | a type can be "retired" in a cycle. They can retire lots of
       | conditional branches at once, or branches and other ops, when the
       | branches are _not taken_.
       | 
       | So it matters whether the code generator produces dead branches
       | that can be retired cheaply. Probably, optimizers take this into
       | account for built-in operations, but they know less about the
       | happy path in libraries.
       | 
       | This is a motivation for the "likely" annotations compilers
       | support. The likely path can then be made the one where the
       | branch is not taken. Code on the unhappy path can be stuck off in
       | some other cache line, or even another MMU page, never fetched in
       | normal operation.
       | 
       | The cost seen here is likely from something else, though. Keeping
       | array size in a register costs register pressure, or comparing to
       | a stack word uses up cache bandwidth. Doing the comparison burns
       | an ALU unit, and propagating the result to a branch instruction
       | via the status register constrains instruction order.
       | 
       | Even those might not be at fault, because they might not add any
       | extra cycles. Modern processors spend most of their time waiting
       | for words from memory: just a few cycles for L1 cache, many more
       | for L2 or L3, an eternity for actual RAM. They can get a fair bit
       | done when everything fits in registers and L1 cache, and loops
       | fit in the micro-op cache. Blow any of those, and performance
       | goes to hell. So depending how close your code is to such an
       | edge, extra operations might have zero effect, or might tank you.
       | 
       | Results of measurements don't generalize. Change something that
       | looks like it ought to make no difference, and your performance
       | goes up or down by 25%. In that sense, the 10% seen here is noise
       | just because it is hard to know what might earn or cost you 10%.
        
         | dathinab wrote:
         | In rust there is `#[cold]` for functions as well as (nightly
         | only) `likely(cond)`/`unlikely(cond)` and some tricks you can
         | have something similar in stable rust.
         | 
         | Also branch paths which lead guaranteed to an panic tend to be
         | treated as "unlikely" but not sure how far this is guaranteed.
        
       | pitaj wrote:
       | Very interesting. One thing that I'm curious about is adding the
       | bounds-check assertion to `get_unchecked` and seeing if that has
       | a significant effect.
        
         | masklinn wrote:
         | Happens from time to time, I've seen folks going around
         | libraries looking for "perf unsafe" and benching if removing
         | the unsafe actually lowered performances.
         | 
         | One issue on that front is a question of reliability /
         | consistency: on a small benchmark chances are the compiler will
         | always trigger to its full potential because there's relatively
         | little code, codegen could be dodgier in a context where code
         | is more complicated or larger.
         | 
         | Then again the impact of the bounds check would also likely be
         | lower on the non-trivial code (on the other hand there are also
         | threshold effects, like branch predictor slots, icache sizes,
         | ...).
        
       | jackmott42 wrote:
       | Occasionally small changes like this will result in bigger than
       | expected performance improvements. An example of this happened
       | once with C#, when two very tiny changes, each of which were
       | borderline measurable, combined they made a big difference.
       | 
       | IIRC it was in the List.Add method, a very commonly used function
       | in the C# core libs. First one programmer refactored it to very
       | slightly reduce how many instructions were output when compiled.
       | Then a second programmer working on the jit compiler
       | optimizations which also affected this Add method making it a
       | little smaller as well.
       | 
       | Alone, each change was hard to even measure, but seemed like they
       | should be a net win at least in theory. Combined, the two changes
       | made the Add method small enough to be an in-lining candidate!
       | Which meant in real programs sometimes very measurable
       | performance improvements result.
       | 
       | As others in this post have noted, a removed bounds check might
       | also unblock vectorization optimizations in a few cases. One
       | might be able to construct a test case where removing the check
       | speeds thing up by a factor of 16!
        
       | downvotetruth wrote:
       | Too much - write vague question titles & get comments in kind
        
       | tayistay wrote:
       | What if a compiler were to only allow an array access when it can
       | prove that it's in bounds? Wherever it can't you'd have to wrap
       | the array access in an if, or otherwise refactor your code to
       | help the compiler. Then you'd have no panicking at least and more
       | predictable performance.
        
         | constantcrying wrote:
         | >What if a compiler were to only allow an array access when it
         | can prove that it's in bounds?
         | 
         | Even very good static analysis tools have a hard time doing
         | this. In a language like C++ this would effectively mean that
         | very few index operations can be done naively and compile times
         | are significantly increased. Performance is likely reduced as
         | well over the trivial alternative of using a safe array.
        
         | glittershark wrote:
         | there's a cheeky link to idris's vector type in the second
         | paragraph: https://www.idris-
         | lang.org/docs/idris2/current/base_docs/doc... which
         | accomplishes just that
        
         | jackmott wrote:
        
         | tylerhou wrote:
         | With bounds checking by default, even if a compiler can't
         | statically prove that an index is in bounds, if the index in
         | practice is always in bounds, the compiler inlines the
         | check/branch into the calling function, and you're not starved
         | of branch prediction resources, the check will be "free"
         | because the branch will always be predicted as taken.
        
         | est31 wrote:
         | Rust has a tool for that, it's iterators. It is only limited
         | however.
        
           | dathinab wrote:
           | also `slice.get()` will return on option
           | 
           | and using a range check manually before an index will
           | normally optimize the internal bounds check away
        
         | nigeltao wrote:
         | That's how WUFFS (Wrangling Untrusted File Formats Safely)
         | works:
         | 
         | https://github.com/google/wuffs#what-does-compile-time-check...
        
       | constantcrying wrote:
       | I imagine the reason bounds check are cheap is because of the
       | branch predictor. If you always predict the in bounds path, the
       | check is almost free.
       | 
       | You also do not really care about flushing the pipe on an out of
       | bounds index, since very likely normal operations can not go on
       | and you move over to handling/reporting the error, which likely
       | has no need for significant throughput.
       | 
       | Also I would just like to note that safe arrays aren't a unique
       | rust feature. Even writing your own in C++ is not hard.
        
         | jackmott42 wrote:
         | Yep, unless your code is wrong, the bounds check will always be
         | predictable. Which makes it free in a sense. But sometimes it
         | will block other optimizations, and it takes up space in the
         | caches.
        
         | pantalaimon wrote:
         | That would be bad on Embedded where MCUs usually don't do any
         | branch prediction.
        
         | dathinab wrote:
         | > If you always predict the in bounds path, the check is almost
         | free.
         | 
         | Note that you often only branch in the "bad" case, which means
         | even on systems without branch prediction it tends to be not
         | very expensive, and compilers can also eliminate a lot of
         | bounds checks.
        
       | Jach wrote:
       | Always amuses me that it's current year and people think about
       | turning off checks, even when they're pretty much free in modern*
       | (since 1993 Pentium, which got like 80% accuracy with its
       | primitive branch prediction?) CPUs...
       | 
       | "Around Easter 1961, a course on ALGOL 60 was offered ... After
       | the ALGOL course in Brighton, Roger Cook was driving me and my
       | colleagues back to London when he suddenly asked, "Instead of
       | designing a new language, why don't we just implement ALGOL60?"
       | We all instantly agreed--in retrospect, a very lucky decision for
       | me. But we knew we did not have the skill or experience at that
       | time to implement the whole language, so I was commissioned to
       | design a modest subset. In that design I adopted certain basic
       | principles which I believe to be as valid today as they were
       | then.
       | 
       | "(1) The first principle was _security_ : The principle that
       | every syntactically incorrect program should be rejected by the
       | compiler and that every syntactically correct program should give
       | a result or an error message that was predictable and
       | comprehensible in terms of the source language program itself.
       | Thus no core dumps should ever be necessary. It was logically
       | impossible for any source language program to cause the computer
       | to run wild, either at compile time or at run time. A consequence
       | of this principle is that every occurrence of every subscript of
       | every subscripted variable was on every occasion checked at run
       | time against both the upper and the lower declared bounds of the
       | array. Many years later we asked our customers whether they
       | wished us to provide an option to switch off these checks in the
       | interests of efficiency on production runs. Unanimously, they
       | urged us not to -- they already knew how frequently subscript
       | errors occur on production runs where failure to detect them
       | could be disastrous. I note with fear and horror that even in
       | 1980, language designers and users have not learned this lesson.
       | In any respectable branch of engineering, failure to observe such
       | elementary precautions would have long been against the law."
       | 
       | -Tony Hoare, 1980 Turing Award Lecture
       | (https://www.cs.fsu.edu/~engelen/courses/COP4610/hoare.pdf)
        
         | dataangel wrote:
         | They're nowhere near free. Branch prediction table has finite
         | entries, instruction cache has finite size, autovectorizing is
         | broken by bounds checks, inlining (the most important
         | optimization) doesn't trigger if functions are too big because
         | of the added bounds checking code, etc. This is just not great
         | benchmarking -- no effort to control for noise.
        
         | titzer wrote:
         | > I note with fear and horror that even in 1980, language
         | designers and users have not learned this lesson. In any
         | respectable branch of engineering, failure to observe such
         | elementary precautions would have long been against the law.
         | 
         | Here we are, 42 years later, and bounds checks are still not
         | the default in some languages. Because performance, or
         | something. And our computers are literally 1000x as fast as
         | they were in 1980. So instead of paying 2% in bounds checks and
         | getting a _merge_ 980x faster, we get 2-3x more CVEs, costing
         | the economy billions upon billions of dollars a year.
        
           | nine_k wrote:
           | Removing bounds checks is a stark example of a premature
           | optimization.
           | 
           | You can remove bounds checks when you can _prove_ that the
           | index won 't ever get out of bounds; this is possible in many
           | cases, such as iteration with known bounds.
        
       | bugfix-66 wrote:
       | Similarly, you can turn off bounds-checking in Go like this:
       | go build -gcflags=-B
       | 
       | and see if it helps. Generally the assembly looks better, but it
       | doesn't really run faster on a modern chip.
       | 
       | Do your own test, and keep the results in mind next time somebody
       | on Hacker News dismisses Go because of the "overwhelming cost of
       | bounds checking".
        
         | masklinn wrote:
         | > next time somebody on Hacker News dismisses Go because of the
         | "overwhelming cost of bounds checking".
         | 
         | That's certainly one criticism I don't remember ever seeing.
        
           | viraptor wrote:
           | There's a few examples like this
           | https://news.ycombinator.com/item?id=32256038 if you search
           | comments for "go bounds checking"
        
       | lowbloodsugar wrote:
       | I am a Rust fan, but 10% degradation in performance (29ms to
       | 33ms) is not "a pretty small change" nor "within noise
       | threshold". If the accuracy of the tests are +/- 10% then that
       | needs to be proven and then fixed. I didn't see any evidence in
       | the article that there is, in fact, a 10% error, and it looks
       | like there is a genuine 10% drop in performance.
        
         | glittershark wrote:
         | a 10% drop in performance with bounds checks _removed_ , mind
         | you - so if anything the bounds checks are improving
         | performance.
        
           | pantalaimon wrote:
           | The more likely explanation is that the test is bunk.
           | 
           | Or maybe the unsafe access acts like volatile in C and
           | disables any optimization/reordering because the compiler
           | thinks it's accessing a register.
        
         | hra5th wrote:
         | To be clear, _removing_ the bounds checks led to the observed
         | performance degradation. Your statement beginning with  "I am a
         | Rust fan, but..." suggests that you might have interpreted it
         | as the other way around
        
           | lowbloodsugar wrote:
           | I certainly did. Thank you for pointing that out. That
           | suggests then that the problem is the tests are bogus.
        
       | tragomaskhalos wrote:
       | I would expect an iterator into a slice to not incur any bounds
       | checking, as the compiler can deduce the end pointer one time as
       | start + size. So idiomatic looping should be maximally efficient
       | you'd hope.
        
         | lmkg wrote:
         | The compiler shouldn't have to deduce anything, an Iterator
         | shouldn't have a bounds check to begin with. It ought to be
         | using unsafe operations under the hood, because it can
         | guarantee they will only be called with valid arguments.
        
           | meindnoch wrote:
           | Calling 'next' on an iterator involves a bounds check.
        
           | tylerhou wrote:
           | Safe iterators have to have bounds checks for dynamically
           | sized arrays; otherwise, how you prevent iterators from
           | walking past the end?
        
             | apendleton wrote:
             | In Rust at least, once you instantiate the iterator, the
             | array it's iterating over can't be mutated until the
             | iterator is dropped, and that can be statically guaranteed
             | at compile time. So you don't need to bounds-check at every
             | access; you can decide at the outset how many iterations
             | there are going to be, and doing that number of iterations
             | will be known not to walk past the end.
        
               | vore wrote:
               | I don't think that's always possible in practice:
               | consider Vec<T>, whose size is only known at runtime. A
               | Vec<T>'s iterator can only do runtime bounds checking to
               | avoid walking past the end.
               | 
               | That said, this is unavoidable in C/C++ too.
        
               | apendleton wrote:
               | I think we're suffering from some fuzziness about what
               | bounds checks we're referring to. Even in your example,
               | you only need to check the size of the Vec<T> when you
               | instantiate the iterator, not each time the iterator
               | accesses an element, because at the time the iterator
               | over the Vec<T>'s contents is instantiated, the Vec<T>'s
               | size is known, and it can't change over the life of the
               | iterator (because mutation is disallowed when there's an
               | outstanding borrow). With a regular for-loop:
               | for i in 0..v.len() {             println!("{:?}", v[i]);
               | }
               | 
               | you check the length at the top (the `v.len()`) and
               | _also_ for each `v[i]`. The first is unavoidable, but the
               | second can be skipped when using an iterator instead,
               | because it can be statically guaranteed that, even if you
               | don 't know at compile time what concretely the length
               | is, whatever it ends up being, the index will never
               | exceed it. Rust specifically differs from C++ in this
               | respect, because nothing in that language prevents the
               | underlying vector's length from changing while the
               | iterator exists, so without per-access bounds checks it's
               | still possible for an iterator to walk past the end.
        
               | tylerhou wrote:
               | When I read "iterator" I think of an object that points
               | into the vector and can be advanced. For Rust's vector,
               | that is std::slice::Iter (https://doc.rust-
               | lang.org/std/slice/struct.Iter.html). When you advance an
               | iterator, you must do a bounds check if the vector is
               | dynamically sized; otherwise, you don't know when to
               | stop. I.e., if I have                 let mut it =
               | vec.iter();       println!(it.next());
               | println!(it.next());       println!(it.next());
               | 
               | This needs to do bounds checking on each call to next()
               | to either return Some(a) or None (assuming the length of
               | vec is unknown at compile time). (hhttps://doc.rust-
               | lang.org/beta/src/core/slice/iter/macros.rs....)
               | 
               | You are right that theoretically a range-based for loop
               | _that uses iterators_ does _not_ need to do bounds
               | checking because a compiler can infer the invariant that
               | the iterator is always valid. In practice I don 't know
               | enough about LLVM or rustc to know whether this
               | optimization is actually happening.
        
               | oever wrote:
               | The Rust compiler guarantees that the memory location and
               | size of the iterated array do not change during the
               | operation. So the iterator can be a pointer that iterates
               | until it points to the end of the array. There is no need
               | to do bounds checks: the pointer only goes over the valid
               | range.
               | 
               | In C/C++, the array _can_ change. It might be moved, de-
               | allocated or resized in the current or a synchronous
               | thread. So the pointer that iterates until it is equal to
               | the end pointer, might point to invalid data if the size,
               | location or existence of the vector changes.
        
               | kaoD wrote:
               | What parent means is that you won't have any bound checks
               | on array access, just a len(arr) loop.
        
       | gigatexal wrote:
       | TL;DR - in the test bounds checking vs no checks showed no
       | noticeable difference. Very good article though. Worth reading.
        
         | carl_dr wrote:
         | Not too long, did read :
         | 
         | The benchmark went from 28.5ms to 32.9ms.
         | 
         | That as a percentage is 15% and is huge, it's not noise.
         | 
         | The test is flawed in some way, the article is disappointing in
         | that the author didn't investigate further.
        
           | dale_glass wrote:
           | MySQL is a huge amount of code doing a variety of things in
           | each query -- networking, parsing, IO, locking, etc. Each of
           | those can easily have significant and hard to predict
           | latencies.
           | 
           | Benchmarking that needs special care, and planning for
           | whatever it is you want to measure. A million trivial queries
           | and a dozen very heavy queries are going to do significantly
           | different things, and have different tradeoffs and
           | performance characteristics.
        
             | carl_dr wrote:
             | The benchmark was specifically testing the hot path of a
             | cached query in their MySQL caching proxy. MySQL wasn't
             | involved at all.
             | 
             | I agree completely that benchmarks need care, hence my
             | point that the article is disappointing.
             | 
             | The author missed the chance to investigate why removing
             | bounds checks seemed to regress performance by 15%, and
             | instead wrote it off as "close enough."
             | 
             | It would have been really interesting to find out why, even
             | if it did end up being measurement noise.
        
       | bjourne wrote:
       | The reason performance decreased when he removed bounds checking
       | is because asserting bounds is very useful to a compiler.
       | Essentially, the compiler emits code like this:
       | 1. if (x >= 0) && (x < arr_len(arr))         2.    get element
       | from array index x         3. else          4.     throw
       | exception         5. do more stuff
       | 
       | The compiler deduces that at line 5 0 <= x < arr_len(arr). From
       | that it can deduce that abs(x) is a no op, that 2*x won't
       | overflow (cause arrays can only have 2^32 elements), etc. Without
       | bounds checking the compiler emits:                   1. get
       | element from array index x         2. do more stuff
       | 
       | So the compiler doesn't know anything about x, which is bad. The
       | solution which apparently is not implemented in Rust (or LLVM,
       | idk) is to emit code like the following:                   1.
       | assert that 0 <= x < arr_len(arr)         2. get element from
       | array index x         3. do more stuff
        
         | vore wrote:
         | I'm not sure I follow: where is abs(x)?
        
           | layer8 wrote:
           | It's an example of what could occur within "do more stuff".
           | The mentioned 2*x is another example.
        
         | est31 wrote:
         | Interesting observation. So one should instead do the
         | comparison with something like:                   1. if (x >=
         | 0) && (x < arr_len(arr))         2.    get element from array
         | index x         3. else          4.
         | core::hint::unreachable_unchecked         5. do more stuff
         | 
         | Where unreachable_unchecked transmits precisely such
         | information to the optimizer: https://doc.rust-
         | lang.org/stable/std/hint/fn.unreachable_unc...
        
       | [deleted]
        
       | titzer wrote:
       | For Virgil, there is a switch to turn off bounds checking, for
       | the only reason to measure their cost. (It's not expected that
       | anyone ever do this for production code). Bounds checks do not
       | appear to slow down any program that matters (TM) by more than
       | 2%. That's partly because so many loops automatically have bounds
       | checks removed by analysis. But still. It's negligible.
        
       | dathinab wrote:
       | Anything between nothing and one most likely correct branch
       | predicted to _not_ jump "branch iff int/pointer > int/pointer".
       | 
       | This kind of bounds check are normally not ever violated (in well
       | formed code) so branch prediction predicts them correctly nearly
       | always.
       | 
       | It also is (normally) just jumping in the bad case, which means
       | with a correct branch predictions thy can be really cheap.
       | 
       | And then cpu "magic" tends to be optimized for that kind of
       | checks at they appear in a lot of languages (e.g. Java).
       | 
       | Then in many cases the compiler can eliminate the checks
       | partially.
       | 
       | For example any many kinds of for-each element iterations the
       | compiler can infer that the result of the conditionally loop
       | continuation check implies the bounds check. Combine that with
       | loop unrolling which can reduce the number of continuation checks
       | and you might end up with even less.
       | 
       | Also bounds checks tend to be an emergency guard, so you tend to
       | sometimes do checks yourself before indexing and the compiler can
       | often use that to eliminate the bounds check.
       | 
       | And even if you ignore all optimizations it's (assuming in
       | bounds) "just" at most one int/pointer cmp (cheap) followed by a
       | conditional branch which doesn't branch (cheap).
        
       | api wrote:
       | It's a lot cheaper than having an RCE and being completely pwned.
        
       | mastax wrote:
       | One technique is to add asserts before a block of code to hoist
       | the checks out. The compiler is usually smart enough to know
       | which conditions have already been checked. Here's a simple
       | example: https://rust.godbolt.org/z/GPMcYd371
       | 
       | This can make a big difference if you can hoist bounds checks out
       | of an inner loop. You get the performance without adding any
       | unsafe {}.
        
         | est31 wrote:
         | Yeah this is because the error message printed contains the
         | location of the error as well as the attempted index. Thus,
         | there are differences between the bounds failures and the
         | optimizer can't hoist the check out (plus probably some
         | concerns due to side effects of opaque functions).
        
         | [deleted]
        
       | tester756 wrote:
       | I've been shocked when I've heard C programmers being actually
       | concerned about performance penalty of checks
       | 
       | like, why bother? CPUs in next 2 years will win that performance
       | anyway
       | 
       | and your software will be safer
        
         | Gigachad wrote:
         | Most online debates are filled with illogical opinions on
         | theoretical issues. You get people on this site complaining
         | that they have to spend money on a catalytic converter because
         | it's not required for the car to run and only prevents other
         | people from getting cancer.
        
         | humanrebar wrote:
         | _All_ of the CPUs? C runs a lot of places.
        
           | tester756 wrote:
           | If you need perf, then consider
           | 
           | better algorithms, better data structures, multi-threading,
           | branchless programming (except safety), data-oriented design
           | 
           | and then elimination of checks, not first.
        
       | rfoo wrote:
       | A consistent 5 ms difference in micro-benchmarks is definitely
       | not "measurement noise". Noise averages out way before
       | accumulating to 5ms. There must be a reason and it mostly likely
       | relates to the change. So you can confidently say that removing
       | bounds checking (at least with how you did it) is a regression.
       | 
       | ... that being said, I'd argue that the most beneficial memory-
       | safety feature of Rust is about temporal things (i.e. prevents
       | UAF etc) instead of spatial ones.
        
         | spullara wrote:
         | A benchmarking harness without error bars?
        
         | whatshisface wrote:
         | Well, there is both random and systemic error in any
         | experiment, and if 5ms is small relative to anything you'd
         | expect (or there is some other reason to discount it) then it
         | might be related to a problem in the benchmarking setup that's
         | too small to be worth resolving. Any test is good to within
         | some level of accuracy and they don't always average out to
         | infinitely good if you rerun them enough times.
        
           | joosters wrote:
           | The 5ms isn't the key number. It's 5ms extra over a 28ms
           | baseline, that's about 18% difference. If your noise
           | threshold is 18%, then I think you have to accept that the
           | benchmark probably isn't any good for this stated task.
        
             | viraptor wrote:
             | https://github.com/bheisler/criterion.rs is good for tests
             | like that. It will give you much more than a single number
             | and handle things like outliers. This makes identifying
             | noisy tests simpler.
        
               | glittershark wrote:
               | The benchmarking harness that the post uses is based on
               | criterion
        
       | piwi wrote:
       | The article mentions measurement noise several times without
       | addressing the uncertainty. It would help to add statistical
       | tests, otherwise the spread could let us conclude the opposite of
       | what is really happened, just because we are out of luck.
        
       ___________________________________________________________________
       (page generated 2022-11-30 23:00 UTC)