Rust can be faster than C because in general C compilers have to assume that pointers to memory locations can overlap (unless you mark them __restrict). Rust forbids aliasing pointers. This opens up a whole world of optimizations in the Rust compiler. Broadly speaking this is why Rust can genuinely be faster than C. Same is true in FORTRAN, for what it's worth.
For what it's worth I did say "can be" not "is" because I wasn't sure of the current state of this feature. I was just passing on the theory.
There are certainly other potential reasons, for instance constant expressions and generics. And, of course, the prohibited undefined behavior makes other optimizations possible, and potentially better.
> C compilers have to assume that pointers to memory locations can overlap, unless you mark them __restrict...
What I don't fully understand is: "GCC has the option -fstrict-aliasing which enables aliasing optimizations globally and expects you to ensure that nothing gets illegally aliased. This optimization is enabled for -O2 and -O3 I believe." (source: https://stackoverflow.com/a/7298596)
Doesn't this mean that C++ programs compiled in release mode behave as if all pointers are marked with __restrict?
restrict and strict aliasing have to do with the same general concept, but aren't the same. They both have to do with allowing the compiler to optimize around assuming that writes to one pointer won't be visible while reading from another. As a concrete example, can the following branches be merged?
Enabling strict aliasing is effectively an assertion that pointers of incompatible types will never point to the same data, so a write to y will never touch *x. restrict is an assertion to the compiler on that specific pointer that no other pointer aliases to it.
OK thanks, indeed Clang is able to generate better assembly using __restrict__. And -O3 generates the same assembly as -O3 -fstrict-aliasing (which is not as good as __restrict__).
I wish there was a C/C++ compiler flag for treating all pointers as __restrict__. However I guess that C/C++ standard libraries wouldn't work with this compiler option (and therefore this compiler option wouldn't be useful in practice).
What's interesting to note though is that I tried marking pointers __restrict__ in the performance critical sections in 2 of my C++ projects and the assembly generated by Clang was identical in all cases!
So while it is true that by default Rust has a theoretical performance advantage (compared to C/C++) because it forbids aliasing pointers I wonder (doubt) whether this will cause Rust binaries to generally run faster than C/C++ binaries.
On the other hand Rust puts security first, so there are lots of array bounds checks in Rust programs (and not all of these bounds checks can be eliminated). Personally I think this feature deteriorates the performance of Rust programs much more than you gain by forbidding aliasing pointers.
Not for char. Compiler always assumes non-restrict for char pointers and arrays, which is important to remember if you're ever operating on a RGB or YCbCr matrix or something.
not according to the standard, but in practice yes, currently. there are arguments that it should be considered not for optimization reasons, but there is likely too much existing code relying on it to change behavior. (see related llvm, gcc bugs)
Well you are saying that even in C you can use the restrict keyword to tell the compiler that 2 memory locations can overlap. Of course is in the hand of the programmer to tell the compiler to do so.
I don't think there is a fair comparison between Rust and C: C is just an higher level assembler, if the programmer knows what he's doing he can use the hardware 100% of its potential. That is the reason why C is still used in all the embedded applications where you have ridiculous low power microcontrollers and you must squeeze out the best performance.
That is the difference between C and Rust to me: for each fast Rust program you are guaranteed that you can write an equivalent performant program in C (or assembly). Worst case scenario you use inline assembly in C and you get that.
Thus the contrary cannot be true for Rust: if I give you a heavily optimized C program not always you can produce an equivalent version in Rust.
Also not always these optimizations are what you want. In C you can choose the level of optimizations, and most of the time, at least on the program that I write, I choose a low level of optimization. The reason is that a lot of time performance is not the only thing that matters, but it maybe matters most the stability of the code (and a code compiler with optimizations is more likely to contain bugs) or the ability to debug (and thus the readability of the assembly output of the compiler).
Rust gives out an horrible assembly code, that is impossible to debug, or to check for correctness. You just have to hope that the compiler doesn't contains bugs. For the same reason Rust is the ideal language to write viruses, since it's difficult to reverse engineer.
> Well you are saying that even in C you can use the restrict keyword to tell the compiler that 2 memory locations can overlap. Of course is in the hand of the programmer to tell the compiler to do so.
It says that they can't overlap, but yes, you can get the compiler to optimize based on this if you provide the aliasing information and remember to keep it accurate. You probably won't do that, though, for anything except the most performance-critical of inner loops. A compiler that can infer more about aliasing can provide more optimization, safely, in the >99% of code that doesn't have explicit aliasing annotations, and that's probably worth some decent speedups in practice.
There are two main things you might be talking about when you call a programming language fast:
1. Given some really performance-critical code and enough time to hand-optimize it, how close can you get the compiler's output to the optimal machine code?
2. If you write code normally, not making any effort to micro-optimize, how fast will it usually be?
Both of these matter. #1 matters when you've got some bottlenecks that you really care about, and #2 matters when you've got a flatter profile -- and both situations are common.
Another illustrative example of the #2 type of performance with Rust is the default collections compared to the ones in C++. Rust's default HashMap is faster than C++'s std::unordered_map because of some ill-advised API constraints in the C++ STL. You can get similar performance by using a custom C++ hash table implementation, and in fact Rust's HashMap is a port of a C++ hash table that Google wrote for that purpose, but most people probably won't bother.
So, a semantic question: if you can get the same speed in one language as you can in another, but in practice usually don't, is one language faster than the other?
> That is the difference between C and Rust to me: for each fast Rust program you are guaranteed that you can write an equivalent performant program in C (or assembly). Worst case scenario you use inline assembly in C and you get that.
Rust also allows inlining assembly.
> Thus the contrary cannot be true for Rust: if I give you a heavily optimized C program not always you can produce an equivalent version in Rust.
This is incorrect for the reason above. Worst case scenario you just inline assembly in Rust to reproduce exactly the same performance of any arbitrarily optimized C program.
I think you'd be hard pressed to find more than a handful of usual C programmers, even in embedded, who know what the __restrict keyword does, let alone are rigorous in its application.
I've always wondered if the reason why Rust keeps running into llvm bugs around restrict is that it is used so sparingly, and the semantics being unclear enough, that these codepaths just aren't exercised as often.
Besides the previous answer, it isn't supported by ISO C++ (although it does exist as extension), because the majority of the WG thinks it will go the same way as register and inline in the long run.
In theory one day Rust will but LLVM doesn't support anything beyond the function level for noalias since that's all that's needed to support __restrict in C. Even that isn't used by Rust most of the time since they have found several bugs in it.