(no title)
OskarS | 1 month ago
However, in the spirit of the question: someone mentioned the stricter aliasing rules, that one does come to mind on Rust's side over C/C++. On the other hand, signed integer overflow being UB would count for C/C++ (in general: all the UB in C/C++ not present in Rust is there for performance reasons).
Another thing I thought of in Rust and C++s favor is generics. For instance, in C, qsort() takes a function pointer for the comparison function, in Rust and C++, the standard library sorting functions are templated on the comparison function. This means it's much easier for the compiler to specialize the sorting function, inline the comparisons and optimize around it. I don't know if C compilers specialize qsort() based on comparison function this way. They might, but it's certainly a lot more to ask of the compiler, and I would argue there are probably many cases like this where C++ and Rust can outperform C because of their much more powerful facilities for specialization.
toodlemcnoodle|1 month ago
Now: the languages may expose patterns that a compiler can make use of to improve optimizations. That IS interesting, but it is not a question of speed. It is a question of expressability.
pessimizer|1 month ago
Saying that a language is about "expressability" is obvious. A language is nothing other than a form of expression; no more, no less.
foldr|1 month ago
I think this is something of a myth. Typically, a C compiler can't inline the comparison function passed to qsort because libc is dynamically linked (so the code for qsort isn't available). But if you statically link libc and have LTO, or if you just paste the implementation of qsort into your module, then a compiler can inline qsort's comparison function just as easily as a C++ compiler can inline the comparator passed to std::sort. As for type-specific optimizations, these can generally be done just as well for a (void *) that's been cast to a T as they can be for a T (though you do miss out on the possibility of passing by value).
That said, I think there is an indirect connection between a templated sort function and the ability to inline: it forces a compiler/linker architecture where the source code of the sort function is available to the compiler when it's generating code for calls to that function.
OskarS|1 month ago
I'm actually very curious about how good C compilers are at specializing situations like this, I don't actually know. In the vast majority cases, the C compiler will not have access to the code (either because of dynamic linking like in this example, or because the definition is in another translation unit), but what if it does? Either with static linking and LTO, or because the function is marked "inline" in a header? Will C compilers specialize as aggressively as Rust and C++ are forced to do?
If anyone has any resources that have looked into this, I would be curious to hear about it.
jarjoura|1 month ago
I guess for your example, qsort() it is optional, and you can chose another implementation of that. Though I tend to find that both standard libraries tend to just delegate those lowest level calls to the posix API.
Measter|1 month ago
C and C++ don't actually have an advantage here because this is only limited to signed integers unless you use compiler-specific intrinsics. Rust's standard library allows you to make overflow on any specific arithmetic operation UB on both signed and unsigned integers.
OskarS|1 month ago
"Culturally", C/C++ has opted for "unsafe-but-high-perf" everywhere, and Rust has "safe-but-slightly-lower-perf" everywhere, and you have to go out of your way to do it differently. Similarly with Zig and memory allocators: sure, you can do "dynamically dispatched stateful allocators that you pass to every function that allocates" in C, but do you? No, you probably don't, you probably just use malloc().
On the other hand: the author's point that the "culture of safety" and the borrow checker in Rust frees your hand to try some things in Rust which you might not in C/C++, and that leads to higher perf. I think that's very true in many cases.
Again, the answer is more or less "basically no, all these languages are as fast as each other", but the interesting nuance is in what is natural to do as an experienced programmer in them.
atoav|1 month ago
I am not saying Rust is faster always. But it can be a damn performant language even if you don't think about performance too deeply or don't twist yourself into bretzels to write performant code.
And in my book that counts for something. Because yes, I want my code to be performant, but I'd also not have it blow up on edge cases, have a way to express limitations (like a type system) and have it testable. Rust is pretty good even if you ignore the hype. I write audio DSP code on embedded devices with a strict deadline in C++. I plan to explore Rust for this, especially now since more and more embedded devices start to have more than one processor core.
jandrewrogers|1 month ago
It is an argument about economics. I can write C that is as fast as C++. This requires many times more code that takes longer to write and longer to debug. While the results may be the same, I get far better performance from C++ per unit cost. Budgets of time and money ultimately determine the relative performance of software that actually ships, not the choice of language per se.
I've done parallel C++ and Rust implementations of code. At least for the kind of performance-engineered software I write, the "unit cost of performance" in Rust is much better than C but still worse than C++. These relative costs depend on the kind of software you write.
throwaway2037|1 month ago
pjmlp|1 month ago
Only if ignoring the C++ compile time execution capabilites.
gf000|1 month ago
I generally agree with your take, but I don't think C is in the same league as Rust or C++. C has absolutely terrible expressivity, you can't even have proper generic data structures. And something like small string optimization that is in standard C++ is basically impossible in C - it's not an effort question, it's a question of "are you even writing code, or assembly".
marcosdumay|1 month ago
Rust defaults to the platform treatment of overflows. So it should only make any difference if the compiler is using it to optimize your code, what will most likely lead to unintended behavior.
kibwen|1 month ago
astrange|1 month ago
On the other hand, writing a function that recovers from overflows in an incorrect/useless way still isn't helpful if there are overflows.
beng-nl|1 month ago
As I don’t see any reason rust would be limited in runtime execution compared to c, I was hoping for this proving an edge.
Apparently not a big of an effect as I hoped.
rcxdude|1 month ago
pornel|1 month ago
It's an interesting optimization, but not something that could be done directly.
ndesaulniers|1 month ago
That's more of a critique of the standard libraries than the languages themselves.
If someone were writing C and cared, they could provide their own implementation of sort such that the callback could be inlined (LLVM can inline indirect calls when all call sites are known), just as it would be with C++'s std::sort.
Further, if the libc allows for LTO (active area of research with llvm-libc), it should be possible to optimize calls to qsort this way.
jesse__|1 month ago
Sure, at the limit, I agree with you, but in reality, relying on the compiler to do any optimization that you care about (such as inlining an indirect function call in a hot loop) is incredibly unwise. Invariably, in some cases it will fail, and it will fail silently. If you're writing performance critical code in any language, you give the compiler no choice in the matter, and do the optimization yourself.
I do generally agree that in the case of qsort, it's an API design flaw
josephg|1 month ago
But we're right to criticise the standard libraries. If the average programmer uses standard libraries, then the average program will be affected (positively and negatively) by its performance and quirks.
vlovich123|1 month ago
In C, you frequently write for loops with signed integer counters for the compiler to realize the loop must hit the condition. In Rust you write for..each loops or invoke heavily inlined functional operators. It ends up all lowering to the same assembly. C++ is the worst here because size_t is everywhere in the standard library so you usually end up using size_t for the loop counter, negating the ability for the compiler to exploit UB.
renox|1 month ago
Then, I raise you to Zig which has unsigned integer overflow being UB.
steveklabnik|1 month ago
Anyway that's a long way of saying that you're right, integer overflow is illegal behavior, I just think it's interesting.
ladyanita22|1 month ago
https://doc.rust-lang.org/std/intrinsics/fn.unchecked_add.ht...
tick_tock_tick|1 month ago
josephg|1 month ago
pjmlp|1 month ago
With C you only have macro soup and the hope the compiler might optimise some code during compilation into some kind of constant values.
With C++ and Rust you're sure that happens.
dana321|1 month ago
quotemstr|1 month ago
jarjoura|1 month ago
trueismywork|1 month ago