5 ms·
It is routine for C++ programs to be substantially faster than a C program that attempts the same job, even though C++ suffers from the same aliasing flaws C ha
by codesushi42 7y ago
It is routine for C++ programs to be substantially faster than a C program that attempts the same job, even though C++ suffers from the same aliasing flaws C has. In the finance world, you would be laughed out of the room for proposing C for a performance-critical task.
That depends on how you write your C++ programs. Virtual functions, runtime type information, STL, RAII ownership can all be performance hits in C++ when compared to C.
Rust has optimization issues of its own apparently.
C isn't a low bar. It is the benchmark.
- jeremyjh 7y agoIs there really a performance cost from RAII? Presumably whatever a destructor has to do to release resources, C code would also have to do.
- comex 7y agoIndeed, RAII is a zero-cost abstraction compared to running the same destructors manually. On the other hand, C++ makes it really easy to write programs that copy and destroy things when it isn't necessary. For example, if a function takes a `std::string` as an argument (by value), any string you pass in will be copied into a new allocation, which will then have to be deallocated. That's fine if the function really needs its own allocation – but it might not. In that case you can avoid the copy by changing the argument type to `const std::string &` or `std::string_view` (the latter being new in C++17)... but the difference is subtle enough that even an experienced programmer might not notice the extraneous copy. Don't believe me? Consider that in 2014, "std::string was responsible for almost half of all allocations in the Chrome browser process"! [1] (Rust does a better job here by requiring more explicitness if you want to make expensive copies.) Oh, there's also an issue where the presence of a destructor pessimizes the calling convention for passing and returning objects of that type by value, but only slightly, and the issue will be addressed in the future. [2] [1] https://groups.google.com/a/chromium.org/forum/#!msg/chromium-dev/EUqoIz2iFU4/kPZ5ZK0K3gEJ https://groups.google.com/a/chromium.org/forum/#!msg/chromiu... [2] https://quuxplusone.github.io/blog/2018/05/02/trivial-abi-101/ https://quuxplusone.github.io/blog/2018/05/02/trivial-abi-10...
- hendzen 7y agoeven if you take the string as `const std::string &` you will still end up with implicit string construction if someone passes a char* . Sure you can use std::string_view as an argument type to prevent this simple case, but it doesn't work in all cases. Consider a std::map<std::string, Blah> for example, if you have a char* and you want to index in to the map you are going to also end up with an intermediate string construction b/c STL associative containers don't have heterogenous lookup. Note that this was fixed in std::map::find in C++14 [0], but still is there for operator[]. [0] - https://en.cppreference.com/w/cpp/container/map/find https://en.cppreference.com/w/cpp/container/map/find
- ncmncm 7y agoThis is obsolete and faulty advice. Given a modern compiler and library, a by-value string temporary can be passed down a chain of calls with no allocations beyond the first, and returned, likewise. Passing a reference means the optimizer cannot optimize accesses, because it doesn't know what other pointers might be aliasing it. string_view has the same problem. Quotes about Chromium and Firefox are likewise obsolete. Old code, old coding standards. Neither uses RAII, so they pay a 20-30% runtime penalty. With a modern library, short strings do no allocation. (IIRC Firefox uses 16-bit characters, so they get less.) That said, if the runtime performance of code trafficking in string objects matters, you are Doing It Wrong.
- comex 7y ago> Given a modern compiler and library, a by-value string temporary can be passed down a chain of calls with no allocations beyond the first, and returned, likewise. For calls (as opposed to returns): If you use std::move, yes. Otherwise, no. But using std::move is similar to changing the type in that it requires noticing the problem first. > Passing a reference means the optimizer cannot optimize accesses, because it doesn't know what other pointers might be aliasing it. string_view has the same problem. For `const std::string &`, the optimizer has to assume that the string pointer and length could change, which is indeed a problem as it has to keep reloading them if you call, e.g., `c_str()` or `size()` multiple times (with other things in between). For both `const std::string &` and `std::string_view`, the optimizer has to assume that the string data could change, but not the pointer or length, which is much less of a problem because you don't usually repeatedly load the same piece of string data in a loop. Therefore, `std::string_view` is a decent choice. Passing `std::string` by value does indeed have the advantage that the compiler indeed could theoretically assume the string data is not aliased. But I just checked and none of Clang, GCC, MSVC, or ICC actually do so: https://gcc.godbolt.org/z/DZPsuU https://gcc.godbolt.org/z/DZPsuU
- fluffything 7y ago> C code would also have to do. Well that's the point: C does not have to do it. RAII protects you from, e.g., leaking memory. But C can just leak memory, and there isn't really anything substantially wrong with that (you can't get UB from leaking memory). Checking error values, releasing all resources, doing bound checks, and all the other things that both C++ and Rust do by default are more expensive than doing nothing. Sure, they are zero-cost, in the sense that if you were to write C code that does those, the C code wouldn't be faster. But as mentioned, C code is not required to do these. With C, you have to manually write the code for doing those things. With C++ and Rust, you have to write the code to opt-out of doing those things (with Rust, removing a bound check is a one liner). It's a matter of language defaults. I know what the better default for me is.
- unrealhoang 7y agoYou can write zero ops destructor as well. So yes, RAII is zero-cost abstraction.
- fluffything 7y agoI literally said so.
- ncmncm 7y agoYou wrote that in C++ and Rust it is necessary to "opt out" of properly freeing memory. That is a false statement. It is not just incidentally false, it is fundamentally false.
- ncmncm 7y agoIf your constructor news memory, then if you don't write a destructor, then nothing deletes the memory. You don't need to "opt out". It is hard to guess where you get this idea. It is bad form to code memory leaks, so normally one doesn't. Instead, one normally allocates in such a way that the automatically-generated destructor frees the memory. But neither case requires writing more code to "opt in" or to "opt out". It is the same in Rust.
- fulafel 7y agoWe all know the acronym stands for "Resource Allocation Is Initialization" - and that in the real world the two often have no reason to be semantically coupled. IOW lots of optimization patterns involve decoupling initialization from allocation and the RAII meme encourages programmers to codify the coupling because C++'s shortcomings make it attractive.
- verall 7y agoThat is what RAII stands for, but in practice good use of C++ RAII is all about explicitly separating interface, allocation, and ownership, such that you only pay for as much initialization as you need when you need it. This takes great care which takes time and leads to slow development. I think of C++ as worth it when this level of care is necessary to create a well-performing system. I would agree that most toy examples don't do a good job of explaining how to use RAII in a careful high performing way
- bt848 7y agoIn what way can “STL” be considered a performance hit? It looks like you just threw out every C++-related word you know.
- ncmncm 7y agoI have carefully benchmarked STL code against C-like loops and never got any measurable difference, even through multiple layers of abstraction. Similarly, virtual function calls on modern CPUs are essentially free if the branch predictor can predict correctly, the usual case. FUD from the '90s is way past its sell-by date.
- verall 7y agoThey probably meant, like everyone else who says that STL is a perf hit, "using STL containers is a perf hit"
- ncmncm 7y agoYou mean, passing along lies, knowingly or otherwise?
- pjmlp 7y agoNowadays, it is ironic given how bad C compilers used to generate code all the way up to the late 90's.
- fluffything 7y ago> C isn't a low bar. It is the benchmark. C isn't the benchmark, assembly is. C forces you to write code closer to what assembly looks like than Rust or C++. That's good if you can write good assembly, and bad if you cannot. That's particularly bad for writing robust code that doesn't have security vulnerabilities, because the C programmer is required to manually write all checks correctly, while C++ and Rust have abstractions that automatically write the checks for you. You don't have to use them in those languages if you don't want to. As I mentioned in the other thread, I think this article is comparing GCC vs LLVM, so the info in the post is kind of moot. But if the authors really believe that Rust bound checks are the culprit of the performance difference, they can just remove them. Takes one line of code, and would show if they are right or wrong.