7 ms·
When is C++ higher-performance than Rust?
by School-Cotton 5y ago
When is C++ higher-performance than Rust?
- bayindirh 5y agoHigh performance and scientific computing, or more generally when you transfer a lot of data structures via pointers to a lot of threads. In that scenarios, a half second performance difference in main loop can translate to hours in long runs. The code I'm developing is running with ~1.7M iterations/core/second. Every one of these iterations contain another set of 1000 iterations or so (the number is variable, so I don't remember the exact mean, but the total is a lot). Also, this number is on an 8 year old system. More benchmarks are here: https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/gpp-rust.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
- ModernMech 5y agoThe link you provided shows C++ and Rust both performing best in various benchmarks. Seems like a wash to me, and to the extent one beats the other it's marginal. Which is to be expected because as long as the language is compiled and the compiler is good enough, you should be able to get as much performance out of it as your CPU can handle.
- bayindirh 5y agoThe numbers might look small, and indeed for most cases it can be considered a wash, but in some cases, these small differences return as hours. For example, I'm using a lot of vector, matrix and array accesses in my code. A lot of these are copied around for computation. Moving that amount of data will trigger a lot of checks in Rust, and will create a slowdown inevitably.
- ModernMech 5y agoDo you have a link to your code that you've benchmarked in C++ and Rust? I'm also doing a lot of vector/matrix/array access in my code, so I'm curious as to what you'd be doing that would cause slow downs to the point that C++ can beat Rust by hours. That would be very enlightening to see.
- bayindirh 5y agoI would love to, but the framework I'm developing is closed source (it's my Ph.D. and postdoc work at the end of the day), but we have a paper for some part of it. Let me see if I can share it.
- School-Cotton 5y agoWhat makes you think Rust forces more “checks” when moving data than C++ does? Can you give an example?
- stouset 5y agoTo wit, the Rust compiler is "sufficiently smart" in an extremely high number of scenarios such that it can achieve optimal performance by eliding things like bounds checks when it's able to determine that they would never fail. This is, in my experience, the overwhelming majority of idiomatic Rust code. For cases where it can't, and performance is absolutely critical, there are easy escape hatches that let you explicitly elide the checks yourself.
- ncmncm 5y ago"Extremely high" means "noticeably many". They are identically the same cases where doing something unsafe in C++ is (likewise) impossible. As such, they drop out of the comparison.
- chlorion 5y agoMoves don't require any runtime checks in Rust to my knowledge. Moves do involve some compile time analysis to make sure you aren't accessing a value that has been moved, maybe that is what you are thinking of? In Rust, moves are always a simple memcpy from one place to another, that's it! With non `Copy` types this destroys the original value which is why the compiler makes sure you don't access it after it's moved. There is also `Clone` which is generally a deep copy of everything, but this also doesn't involve any runtime checks as long as you aren't using reference counted pointers or something. There are bounds checks when indexing into vectors and whatnot though, but non checked methods are available.
- ncmncm 5y agoIn other words, moves in Rust are very limited and limiting.
- chlorion 5y agoThey are more limited than C++ moves for sure, whether that is a good or a bad thing I am not so sure of! I am curious if there are any examples of things you wanted to do but couldn't with Rusts move semantics. After using both C++ and Rust I personally feel that the way Rust handles moves makes a lot more sense and ends up being much more ergonomic. In C++ moves feel messy and complex due to them being added on rather than having the language built around them from the start. It's also not ideal that C++ moves must leave values hanging around in an unspecified state rather than destroying them immediately, even if there are a few cases where that is useful, most of the time it's not. Rust also doesn't have any of the crazy `&&T` rvalue reference stuff or require explicitly defining move constructors since everything is movable and all moves are bitwise copies.
- steveklabnik 5y agoThe classic thing that's tough to do is self-referential structures; since you have no hook into moves, you can't fix-up the references. It also has quite a few pros, and many people think it's better the way it is. Since moves are memcopies, they can never fail, meaning there's no opportunities for panics, which means it's much easier to optimize. They aren't arbitrarily expensive; you always know what the cost is. As you mentioned, there's no need for a "moved-from" state, which can save space and/or program logic.
- School-Cotton 5y agoYou haven’t actually explained when Rust is slower. You just described a situation where you care about performance. What specific operations happen slower in Rust than in C++?
- deleted 5y ago[deleted]
- jcelerier 5y agoRust's Vec indexing is bound-checked by default. Just something like this is abysmal for performance (but pretty good for consultants who get called to fix stuff afterwards, so, like, keep them coming!): - Rust: https://rust.godbolt.org/z/GK8WY599o https://rust.godbolt.org/z/GK8WY599o - CPP: https://gcc.godbolt.org/z/qvxzKfv8q https://gcc.godbolt.org/z/qvxzKfv8q
- School-Cotton 5y agoBoth languages let you access vector elements with or without bounds checking. Stroustrup even recommends you use .at by default in c++.
- titzer 5y ago> Rust's Vec indexing is bound-checked by default. Just something like this is abysmal for performance Unless you can provide numbers to back this claim up, I'll continue to rely on my measurements that bounds checking (in Virgil) costs anywhere from 0.5% to 3% of performance. It's sometimes more or less in other languages, but it is by far not the reason any programming language has bad performance. I have no reason to suspect that it costs more in Rust, other than misleading microbenchmarks.
- jcelerier 5y agoHere's a quick and dirty example: https://gcc.godbolt.org/z/3WK7eYM4z https://gcc.godbolt.org/z/3WK7eYM4z I observe (-Ofast -march=native -std=c++20 ; CPU is intel 6900k with performance governor on Arch... blasting at 4GHz) : - clang 13: 8.93 ms per iteration - gcc 11: 9.46 ms per iteration so roughly around 9ms. Replacing return mat[r * matrix_size + c]; by return mat.at(r * matrix_size + c); I observe - clang 13: 20.95 ms per iteration - gcc 11: 18.11 ms per iteration so literally more than twice as slow. I also tried with libc++ instead of libstdc++ for clang and the results did not meaningfully change (around 9ms without bound-checking and 21 ms with).
- titzer 5y agoFrom the godbolt link, it looked like most of the vector operations were not getting inlined. You'll need -O2 or higher to have representative results. I could counter by just writing a Virgil program and turning off bounds check with a compiler flag. We could stare at the machine code together; it's literally an additional load, compare, and branch. The Virgil compiler loves eliminating bounds checks when it can. I know your original comment was in the context of the STL, but it really muddies the waters to see huge piles of code get inlined or not depending on compiler flags. Machine code is what matters. Regardless, this is still microbenchmarking. Maybe matrix multiply is an important kernel, but we need to benchmark whole programs. If I turn off bounds checks in a big program like the Virgil compiler, I cannot measure more than about 1.5% performance difference.
- nivenkos 5y agoThere's no placement new IIRC so you always build on the stack and copy it to the heap. https://users.rust-lang.org/t/how-to-create-large-objects-directly-in-heap/26405 https://users.rust-lang.org/t/how-to-create-large-objects-di...
- andrewaylett 5y agoI think that's a question of an "insufficiently smart compiler" rather than necessarily something the programmer should be concerned about?
- Koshkin 5y agoThe programmer should certainly be concerned about the compiler being insufficiently smart.
- steveklabnik 5y agoNo, it is a language semantics issue. You can get the compiler to optimize it away today, if you're careful. But that's a bad user experience. You should be able to specifically request this semantic, and be sure it works.
- School-Cotton 5y agoFair point.
- tialaramex 5y agoYou can do "placement new" (Rust has no new operator, but in this context) unsafely with MaybeUninit -- https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.h... Make a MaybeUninit<Doodad> on the heap, initialise it, and then unsafely assume_init() remembering to write your safety justification ("I initialised this, so it's fine") and get a Doodad, on the heap. The reason it isn't mentioned in that 2019 u.r-l.o post is that MaybeUninit wasn't stabilized until mid-2019.
- jandrewrogers 5y agoI would frame the question more in terms of economy than performance. In theory, I could write a database engine in Rust that is as performant as C++, but it would not make sense to. Making Rust perform the same as C++ in database engines requires marking large portions of the code base as "unsafe", while requiring substantially more lines of code due to being less expressive. Note that this doesn't imply that the code is actually unsafe, just that the Rust compiler cannot reason about the safety of correct and idiomatic code in database engines. And this is why state-of-the-art database engines are still written in C++. There is not much point in using Rust if doing so requires disabling the safety features and writing a lot more code to achieve the same result.
- pkolaczk 5y agoDefinitely not my experience with file io and database-related software in rust. Actually two of my publically available programs - fclones and latte - seem to be the most efficient in their respective classes, beating a wide variety of C and C++ programs, yet they contain almost no unsafe code (< 0.1%). The state of the art database engines are written in C++ (and Java) because at the time they were developed Rust didn't exist.
- jandrewrogers 5y agoAll high-performance database engines do full kernel bypass -- Linux has standard APIs for this. Databases designed this way are much higher performance, which is people do it. It may not be obvious what this has to do with Rust. The downside of kernel bypass is that the kernel is no longer transparently hiding the relationship between storage, memory, and related low-level hardware mechanics. A consequence of this is that the mutability and lifetime of most objects cannot be evaluated at compile-time, which Rust relies on for its safety model. The hardware can hold mutable references to memory that are not represented in software and which don't understand your object model. How do you manage lifetimes when references to objects are not visible in the code? When an object does not have a fixed address? And so on. This is not trivial even in C++ but the language does allow you to write transparent abstractions that hide this behind something that behaves like a reference without complaining. There are proven models that guarantee safety under these constraints, widely used in database engines. Instead of ownership semantics, they are based on dynamic scheduling semantics. The execution scheduler can dynamically detect unsafe (or other) situations and rewrite the execution schedule to guarantee safety and forward progress without violating other invariants. This is why, as a simple example, some databases never seem to produce deadlocks -- deadlock situations still occur but they are dynamically detected before they occur and are resolved transparently by editing the lock and execution graph. Some major classes of database architecture optimization don't work in garbage collected languages. For this reason, you never see state-of-the-art database engines written in e.g. Java.
- infamouscow 5y agoRust's lack of fast thread-local storage makes it a non-starter for me. It's really disappointing, especially when the language has incorporated so many other excellent performance improvements like Google's SwissTable and pattern-defeating quicksort. https://github.com/rust-lang/rust/issues/29594 https://github.com/rust-lang/rust/issues/29594 https://matklad.github.io/2020/10/03/fast-thread-locals-in-rust.html https://matklad.github.io/2020/10/03/fast-thread-locals-in-r...
- roblabla 5y agoFWIW, Rust does have fast thread locals, but only in nightly. https://github.com/rust-lang/rust/issues/29594 https://github.com/rust-lang/rust/issues/29594
- infamouscow 5y agoHow many companies are running Rust nightly in production?
- roblabla 5y agoAccording to the 2020 Rust Survey[0], a decent amount. The majority is on stable rust, but a bit less than a third of rust users are on nightly. The company I work at uses a pinned nightly rust in order to access some soon-to-be-stabilized features that simplify our life a bit. We update our pinned nightly in lockstep with stable rust releases. In practice, we've almost never had problems with using nightly rust - those are still very well tested, and problems are caught early and fast. The Rust test suite is quite thorough! However, we do try to avoid using features that don't have a clear trajectory towards stabilization, so for us to consider thread_locals, we'd need to have some very solid proof that it would fix some critical performance problems. I suspect every org will have their own policy, and while the majority will use stable rust, it's not like nightly rust is completely unthinkable. [0]: https://blog.rust-lang.org/2020/12/16/rust-survey-2020.html https://blog.rust-lang.org/2020/12/16/rust-survey-2020.html
- 5y ago