Hacker News new | past | comments | ask | show | jobs | submit login

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.



Why does the Rust compiler not optimize code assuming that two mutable references cannot alias? —https://stackoverflow.com/q/57259126/155423


Follow along here for re-enabling: https://github.com/rust-lang/rust/issues/54878

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?

  void foo(/*restrict*/ bool* x, int* y) {
    if (*x) {
      printf("foo\n");
      *y = 0;
    }
    if (*x) {
      printf("bar\n");
    }
  }
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.


> so there are lots of array bounds checks in Rust programs

Depends on how those programs were written. Iterators should avoid bounds checking for example.


Likely most C code, particularly data structure code, would break if compiled with a setting that treats all pointers as restrict.


I think C and C++ have enough problems with accidental undefined behavior already without making aliased pointers into UB.


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.


Huh.

Does that also hold for a "uint8_t"--which is often just a renamed unsigned char rather than being a genuine type of its own?


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)


Yes


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?


> but most people probably won't bother.

Most people who don't bother with that probably also don't bother with the performance of their hashmap.


People think C is an high level Assembler, that was only true on PDP-11, 8 and 16 bit CPUs.

Also in C you cannot retrofit restrict in existing programs, specially they depend on existing binary libraries.


> 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.

Do you have some evidence here? Or something more specific?

(The rest of your comment is opinions, which you can of course have, but does not match my personal experience, FWIW.)


There's a translator for C99 to (unsafe) Rust: https://github.com/immunant/c2rust

So probably you can give me any C program and I'll be able to give you an equivalent Rust program. It'll probably perform about the same, too.


> 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.


> For the same reason Rust is the ideal language to write viruses, since it's difficult to reverse engineer.

Eh, not really.


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.


LLVM does support tbaa! and noalias! metadata.





Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: