C++ Core Guidelines
C++ Core Guidelines
Posted Apr 18, 2021 17:29 UTC (Sun) by mathstuf (subscriber, #69389)In reply to: C++ Core Guidelines by mss
Parent article: Rust in the Linux kernel (Google security blog)
This should be *may* exist. The thing about performance is that it usually needs actual measurements when the concepts are at least close in principle. You don't need it for comparing Python's interpreter for loop execution versus an AVX-optimized loop from C, but if a JIT gets involved or you have two languages compiling down to AVX-optimized code, such speculation and fear-mongering is unwarranted at this stage.
Posted Apr 18, 2021 18:06 UTC (Sun)
by Wol (subscriber, #4433)
[Link] (9 responses)
> This should be *may* exist.
Exactly. It is a PREREQUISITE for a trade-off, that an increase in one is balanced by a decrease in the other. If the increase in safety comes at no cost to performance (as seems quite likely to be the case here) then no trade-off can exist.
It seems Rust's safety claims come at a compile-time cost, but there is no reason why there should be a run-time performance cost.
Cheers,
Posted Apr 18, 2021 23:50 UTC (Sun)
by mss (subscriber, #138799)
[Link] (8 responses)
Let's consider, for example, index-is-in-bounds enforcement.
A simple example (pseudocode):
SHA256() here is a placeholder for any complex transformation.
I don't see how one can enforce a perfect memory safety here without adding an extra bounds check on "idx" before accessing the "vals" array.
At best it makes code bigger, and as you rightly had said in another comment here:
Posted Apr 19, 2021 8:59 UTC (Mon)
by Cyberax (✭ supporter ✭, #52523)
[Link] (6 responses)
> Even though the programmer writing this function might have known that it will never be called with a value that would result in an out-of-bounds access.
A bounds check is extremely cheap, since they are almost always correctly predicted by the CPU. There's no real reason to avoid them. I think Microsoft did an experiment with C# and removing all range checks resulted in a 1% overall performance improvement.
Posted Apr 19, 2021 11:59 UTC (Mon)
by excors (subscriber, #95769)
[Link]
I think it'd be more accurate to say there's no real reason to avoid them *by default*. Very occasionally you might write some code where it has a large enough impact on system performance that it's worth optimising, and it's important that the language lets you optimise those cases without too much pain.
E.g. https://coaxion.net/blog/2018/01/speeding-up-rgb-to-grays... has an example of an image processing loop where some bounds checks can be eliminated by simply adding asserts before the loop, and the rest can be eliminated by defining a new iterator type, giving a 2.2x speedup. And there's always the possibility of doing non-bounds-checked accesses in unsafe (/not-proved-safe-by-the-compiler) blocks, wrapped with some higher-level logic to guarantee it is still safe, as a last resort.
That seems no worse than optimising C/C++ code, where various aspects of the language (aliasing, lack of inlining across translation units, template code bloat, etc) tend to hurt performance across your whole codebase but the effect is so small that you can usually ignore it; and in the few cases where it really matters, you can tweak the code to encourage the compiler to optimise it better (restrict, inline function definitions in headers, type erasure, etc). A high-performance language isn't one where code automatically has high performance - it's one where most code has adequate performance, and then the language plus tools (profiler, compiler, etc) can work effectively in a feedback loop with the programmer to optimise the parts that need it, and it sounds like Rust does okay at that.
(Incidentally C++ has a more serious performance problem in how its "zero-overhead principle" is violated by exceptions and RTTI - that's reported as increasing code size by typically 15% and sometimes ~40% (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p...), even in code that never throws an exception. That's something you can't fix locally after identifying bottlenecks; you have to disable exceptions globally, and reject libraries that rely on exceptions (like STL and much of the rest of the C++ standard library). So in code where you're worried about every byte, standard C++ is not a good solution. C++-without-exceptions is okay but sacrifices some of the advertised benefits of modern C++. I don't know if Rust has any similarly expensive misfeature.)
Posted Apr 19, 2021 13:01 UTC (Mon)
by mss (subscriber, #138799)
[Link] (4 responses)
With sufficiently complex transformation the compiler wouldn't be able to infer the input range that the valid output range corresponds to.
> The actual reasonable answer is: DON'T DO THIS!!!! EVER!
That's entirely valid code if one can guarantee that it will always get a valid input value.
> A bounds check is extremely cheap.
Might be cheap or might be expensive, there isn't a guarantee of that on every arch the code will run on.
Posted Apr 19, 2021 13:18 UTC (Mon)
by mathstuf (subscriber, #69389)
[Link]
Then you put an `assert()` in there. The compiler can use it as a hint for optimization purposes. But leaving this open is just asking for trouble and is exactly the kind of thing that causes memory safety problems in real code.
The point is that *you* might have verified this, but you did not do either of telling the compiler this (via an `assert`) or, *far* more importantly, leave a comment for future developers that come across this code. If I came across this, I would add the check-or-bail because it's just inherently bad code. A comment of "this function always works for our inputs" would have me instead adding an assert to ensure that it is the case (at least in debug builds).
Posted Apr 19, 2021 18:36 UTC (Mon)
by Cyberax (✭ supporter ✭, #52523)
[Link] (2 responses)
> That's entirely valid code if one can guarantee that it will always get a valid input value.
Posted Apr 19, 2021 18:41 UTC (Mon)
by mathstuf (subscriber, #69389)
[Link] (1 responses)
"Whatever" is not valid (because you still need to uphold Rust's invariants even in `unsafe` blocks[1]) and I feel that such characterizations mislead those who are not familiar with Rust about what it actually means. In this case, `get_unchecked` (an unsafe method on Vec and slices that can be called from within an `unsafe` block) would be what is wanted.
[1] For example, `bool` must be a 0 or 1 representation. Even unsafe cannot store the integer value of `2` into such a location viewed through a `bool` lens because there could be code that does `== 1` for truth comparison in one place and `== 0` in another which means that the `bool` is neither which is no good.
Posted Apr 20, 2021 9:17 UTC (Tue)
by farnz (subscriber, #17727)
[Link]
It's "whatever" in the same sense as C or C++. You can do whatever you like, but the compiler does not have to understand it the way you wanted it to, just as a C compiler is not compelled to interpret x = ++x + x++ + x - x-- + --x the way you want it to.
Posted Apr 19, 2021 10:45 UTC (Mon)
by mathstuf (subscriber, #69389)
[Link]
In C++, one would need to manually add the bounds check to get that safety. The compiler might optimize it away, but usually based on code contexts. Here, C++ and Rust are in the same boat for performance (assuming one doesn't want to handle a more-than-DoS CVE in the future for the C++ code).
On a side note, if you're trying to be honest in this discussion, you don't get to show busted code then saying "C++ can do this faster". No duh. That C++ has bigger footguns readily offered to developers is not news. It is this pattern of letting developers write busted code much more easily that is the *primary* focus for adding a new language; that's why Rust is so interesting.
C++ Core Guidelines
Wol
C++ Core Guidelines
> string idx2s(int inval) {
> string vals[] = { "foo", "bar", "xyz" };
> int idx = SHA256(int2str(inval))[0];
>
> return vals[idx];
> }
Even though the programmer writing this function might have known that it will never be called with a value that would result in an out-of-bounds access.
I don't see how one can guarantee absolutely no runtime performance impact of this extra check - especially that it is likely that there will be at least a few sites like this in a large program (which Linux kernel is).
> You also need code NOT to go over a certain size to avoid cache-line bouncing.
> Accidentally pulling in those few extra bytes into your subroutine's code can slow execution by an order of magnitude.
C++ Core Guidelines
If a compiler knows that SHA256 returns only values from the [0;2] range then it can elide the check. You can now do that via const generics in Rust (in experimental, will be stabilized by the next release).
The actual reasonable answer is: DON'T DO THIS!!!! EVER!
C++ Core Guidelines
C++ Core Guidelines
That's why I have written that the SHA256() call in the example code is a placeholder for a complex transformation.
I understand that making this guarantee might be hard, but it's a trade-off.
The only thing certain is that a check that isn't there doesn't use any execution resources.
C++ Core Guidelines
C++ Core Guidelines
The transformation writer needs to supply the bounds. It's not possible all the time, but it is possible in a surprising number of times. Newly introduced const generics can help with that.
> I understand that making this guarantee might be hard, but it's a trade-off.
The tradeoff is just not worth it for the overwhelming majority of code. But if you are super-certain that it's OK, you can slap an "unsafe{}" block and do whatever you want.
C++ Core Guidelines
C++ Core Guidelines
C++ Core Guidelines