24 ms·
Why is Rust slightly slower than C?
- gameswithgo 7y agoI wonder if some of the bounds checks could be eliminated by using iterators instead of loops? It is common when coming from C to Rust to sometimes avoid complicated iterators because you imagine it can't be fast, so you use a loop, but usually the iterator really is faster. And I believe the checked math can be eliminated just by explicitly stating you want to use unchecked math. It doesn't require unsafe to do so.
- holy_city 7y ago>I wonder if some of the bounds checks could be eliminated by using iterators instead of loops It can and often is. Don't use [] to index into data if you can afford not to. Anecdotally, rustc is also much better at generating SIMD-friendly code with iterators than idiomatic C/C++, but that depends largely on what you're doing.
- The_rationalist 7y agoSource?
- viraptor 7y agohttps://llogiq.github.io/2017/06/01/perf-pitfalls.html https://llogiq.github.io/2017/06/01/perf-pitfalls.html https://doc.rust-lang.org/1.30.0/book/second-edition/ch13-04-performance.html https://doc.rust-lang.org/1.30.0/book/second-edition/ch13-04...
- The_rationalist 7y agoI see no comparison about rust vs c++ performance in general nor about auto vectorization.
- ShorsHammer 7y agoAnyone asking for "source" should be able to prove that they've not searched first. Because it's right there. As noted in the other comment it's in the literal documentation for rust. Low effort commenting is usually shunned here, yet the ever prevalent "source?" seems to get a free pass all the time. Really people should say: "source? Because I tried https://www.google.com/search?q=rustc+is+also+much+better+at+generating+SIMD-friendly+code https://www.google.com/search?q=rustc+is+also+much+better+at... and found no sources whatsoever about this issue that has been discussed to death on dozens of forums yet I was somehow incapable of finding a single thing on the matter" Why should someone spend time typing out easily searchable content for you? In fact, why should forums with a focus on high quality content even answer you? Anyone who asks for sources should be required to do the same on demand. Whenever, wherever.
- DoreenMichele 7y agoI've certainly seen requests for sources used obnoxiously by people who felt zero need to back up their side of the argument with sources. But it's also often a legitimate request to see what the OP is talking about. The internet is vast. I sometimes have difficulty finding things I personally saw before and know exist. Slightly different search terms can turn up drastically different results and, with five million visitors per month to HN, it is a melting pot of people with vastly different backgrounds.
- leksak 7y agoCould it not be an educational tool to the person asked that when they write a statement of a certain kind a source citation should really accompany it?
- The_rationalist 7y agoWell I mostly agree when the first Google result answer the claim. I've clicked on most links from your Google query and was not able to find something that prove Anecdotally, rustc is also much better at generating SIMD-friendly code with iterators than idiomatic C/C++ Most of the links are about explicit SIMD, something that is off topic with the initial claim and something that c/c++ do far better (so many SIMD libs), and are also better at hybrid SIMD (SPMD, OpenMP 5,o penACC, etc). "Anecdotally, rustc is also much better at generating SIMD-friendly code with iterators than idiomatic C/C++" The author talk about auto vectorization. Firstly c/c++ supports AVX512 while rust do not (in stable at least) Secondly real world llvm/gcc dev are paid to improve c/c++ performance, rust performance improvements are only a side effect. And iterators in c++ are idiomatic and I see no reason why rust iterators would auto vectorize better than c++ ones. And c++ do not enforce bounds check (but allow it with at(), anyway bounds check are a totally useless concept because they slow down release performance and capture far less information for debugging than ASAN) So the initial claim is non obvious and I see no answer on the Google query. If nobody is able to prove it I will believe the claim is probably wrong.
- GordonS 7y agoAs an aside, I seem to recall .NET making an interesting optimisation here, such that if you access an array using, for example, `data[20]`, bounds checks are omitted for lower indexes.
- shepmaster 7y agoRust and/or LLVM will make this optimization as well. The easiest example I can find of this is in the buffered IO code [1], but there have been others [1]: https://github.com/rust-lang/rust/blob/f0b58fcf03391a91f74224fe38a696d5a5b789d9/src/libstd/io/buffered.rs#L277-L278 https://github.com/rust-lang/rust/blob/f0b58fcf03391a91f7422...
- exrook 7y agoThere's a similar pattern in rust: fn loop(a: &[u8]) { assert!(a.len >= 20); for x in (0..20) { let y = a[i]; //Do something with y } } Adding the assert before the loop will remove the bounds checks happening on each iteration.
- hcs 7y agoInteger overflow checks are disabled by default in release builds, the author noted they were explicitly enabled in this project for additional safety. (This is just for the normal operators, if you use e.g. checked_add() or wrapping_add() then you can be explicit about what behavior you want.) [edit: oops, the overflow checks were only enabled for a separate test, see note from the author below]
- emmericp 7y agoNo, we don't run with overflow checks by default; the overflow check performance analysis is a separate test.
- ridiculous_fish 7y agoIt's worth observing that signed integer overflow is undefined in C but defined in Rust, which can impose an additional cost. C: https://godbolt.org/z/CDha8- https://godbolt.org/z/CDha8- Rust: https://godbolt.org/z/DlN3uf https://godbolt.org/z/DlN3uf
- jnordwick 7y agoiterators had a lot of work done on them for this reason. although it doesn't always work. eg, once the difference between using a sign and unsigned value in a loop was causing tustc to not be able to optimize out the checks and resulted in terrible performance for that loop, but it was non-trivial to figure this out
- im3w1l 7y agoI'm surprised how much bloat rusts adds and how little it affects the speed. Is there any other downside? Electricity consumption / heat? Evicting other stuff from cache?
- webkike 7y agoLLVM is a really good backend that optimizes away a lot of it. Bounds checks, for example, usually get removed.
- wyldfire 7y agoWhat bloat are you referring to specifically?
- grenoire 7y agoI think GP forgot to build with the release flag instead of debug.
- muricula 7y agoIf they built with debug there would be dramatically more load/store uops than you see in the benchmark. Debug mode builds disable optimizations and store variables back to memory after most expressions to aid debugging.
- im3w1l 7y agoI'm referring to the table in the article.
- wyldfire 7y ago> Our Rust driver is a few percent slower than our C driver Please qualify the title: it's not generally the case.
- deleted 7y ago[deleted]
- tom_mellior 7y agoLooking at performance counter data is good, but I would have liked to see a real validation of the hypothesis that bounds checking is to blame for the extra branches and instructions. That is, modify the Rust compiler to not emit bounds checks (or maybe there is even a flag for this?) and look at performance and counters. I would imagine that this would bring the data for Rust to pretty much the same as C. But other compiler (micro-)optimizations might be at play as well. Also, from the paper's Conclusions: "The cost of [Rust's] safety and security features are only 2% - 10% of throughput on modern out-of-order CPUs." 10% network throughput is a lot.
- steveklabnik 7y ago> (or maybe there is even a flag for this?) We specifically do not give you a flag to control this behavior, but you can choose to call different functions to use unchecked access.
- sh1mmer 7y agoI'm curious if there is a discussion thread on that design choice.
- steveklabnik 7y agoI’m not 100% sure if there’s a source exactly, but we don’t like safety and correctness to depend on what flags you pass or do not pass. We don’t offer a fast-math flag either for similar reasons. The odd one out is overflow, and that’s only because it’s well defined (a “program error”) and not UB to overflow in Rust. This gets checked in debug but not currently release, though the spec allows for it.
- sh1mmer 7y agoI guess my thought is that much of correctness comes from the compiler being able to make assertions that some type (and thus some memory address) will only be used in a correct way at compile time, etc, etc. For example if we were dynamically linking a Rust crate into a Rust binary is it necessary to check boundaries in both or can some of that be deferred because we can assume the binary that will link has already done the boundary checks, etc? I know it's a bit contrived since ideally we'd just compile statically, but I think it's still potentially valid. If both pieces of software have the guarantees then ideally you can factor out some of the overhead.
- viraptor 7y agoI wish they included rust benchmark code so we can play around and improve it. If the issue really is the bounds checks, then there are many ways to massage Rust code to make it obvious they're not needed. But it seems you can't really play with it without running on actual hardware.
- emmericp 7y agoReally no point to running it without real hardware, it will perform completely differently if you don't have the MMIO accesses and DMA by the NIC in there. We'll have a VirtIO driver soon, but that's bottlenecked by the hypervisor and not really useful for performance tests.
- viraptor 7y agoWith fake memory mapping it would not access the same memory, but you'd still run the same code, right? So the timing/details of access may change a lot, but the profile of executed code would be still meaningful. For example, if you run a loop with range checks, and a loop without them, it doesn't matter what's happening in the loop, as long as it stays consistent between the runs - you can still tell things about the loop overhead. Or did I miss something more nuanced?
- emmericp 7y agoWould need a somewhat realistic emulation of the NIC setting/clearing these flags. Also, the by far slowest steps are the MMIO access because it involves a full PCIe round trip which is hard to emulate. It will behave different at the level we are looking at here. It's easier to use real hardware; we specifically chose this NIC because it's probably the most common 10G NIC on servers.
- NickMar 7y agoThey have provided the code :https://github.com/ixy-languages/ixy.rs https://github.com/ixy-languages/ixy.rs and the benchmark script : https://github.com/ixy-languages/benchmark-scripts https://github.com/ixy-languages/benchmark-scripts . I found that in the readme of the repo containing the article.
- aloknnikhil 7y agoIt's be interesting to see how Rust performs compared to vector packet processing (https://fd.io/technology/ https://fd.io/technology/). My hunch is, it's not very easy to implement something similar in Rust that can keep the instruction cache hot, especially going by the data from this paper.
- fluffything 7y agoBecause C is slower than C. That is, they are compiling C with GCC, but Rust uses LLVM as a backend. Compiling their C code with clang reveals that it is also slightly slower than the C code compiled by GCC, but not faster than Rust. The actual conclusion here is that the GCC toolchain is slightly faster than the LLVM toolchain for this particular use case, but that isn't really news - it happens all the time. If one want to focus this on Rust, then the downside of Rust vs C here is that C has a GCC toolchain that happens to be faster for this use case, but Rust does not.
- emmericp 7y agoThank you for your thoughtful comment, recommended reading: https://news.ycombinator.com/newsguidelines.html https://news.ycombinator.com/newsguidelines.html (edit: thanks for editing your comment, we can have a civil discussion here) To answer your question: Yes, that particular benchmark was done with GCC and yes, it would have been better to use clang/LLVM for a more fair comparison. We of course also compared gcc with clang/LLVM and found that clang was 0.8% slower, so it does not explain the difference entirely.
- fluffything 7y ago> We of course also compared gcc with clang/LLVM and found that clang was 0.8% slower, so it does not explain the difference entirely. I think it would be quite useful and interesting to figure out why there is still a small difference between clang and Rust. There might be some low hanging fruit in how Rust generates LLVM-IR that could be fixed.
- emmericp 7y agoAnother thing to point out: our 6% - 11% difference is already quite low. Netbricks [1] has a similar comparison between a Rust and a C network function (only the NF, driver in C in both cases) and they find 14% (LPM) to 20% (synthetic NF) difference. Fun fact: we've a system where Rust is faster than C despite still using more instructions, so yeah, neither instructions nor cycles tell the whole story... [1] https://people.eecs.berkeley.edu/~apanda/assets/papers/osdi16.pdf https://people.eecs.berkeley.edu/~apanda/assets/papers/osdi1...
- asdkhadsj 7y agoAs an aside, can I just say how happy I am that the language I generally find a joy to use is nearly as fast as C? It's my daily driver, and I have almost no complaints. I really do love this language.
- physicsyogi 7y agoThere are a few numerical computing benchmarks for which Rust is sometimes faster than C: https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/rust.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
- emmericp 7y agoGeometric mean over their "fastest measurement at the largest workload" data set actually shows that Rust is faster than C in this benchmark
- igouy 7y agoPlease share! If true, can you say the difference is not due to chance? (fwiw the GM that I see — whether the scores are based-on elapsed secs, cpu secs or "busy" secs — are a little slower for the Rust programs.) Perhaps the median score of the Rust programs is faster?
- jamessteel 7y agoFor what it worth, it is more accurate to bench on your own machine, you may find a different or interesting outcomes such as error and slower in Rust.
- graycat 7y agoOn how "fast" C is, guys, sorry to say this, but in my experience actually (1) even assembler is not very "fast", (2) for much the same reasons C is not very fast, (3) often Fortran is faster than C, and (4) PL/I can be the fastest of all of (1)-(4). More generally, how Fortran and PL/I can be faster than C should be exploited quite generally but apparently has not been fully generally. Why? How can Fortran and PL/I beat assembler and C? In particular, first, Fortran can easily beat C in subroutines that receive multidimensional arrays as parameters. Here PL/I also can beat C for the same reasons. Second, PL/I beats both Fortran and C in string handling. How, why, what the heck is going on? In simple terms, first, Fortran gets to compile the multidimensional array handling within the language and C does not: In C, the programmer has to write the array addressing logic (e.g., row major or column major) that looks to the C compiler like just more code where the compiler has to treat that handling as general purpose code; the Fortran compiler KNOWS that the work is array handling and gets to make better uses of registers and to store intermediate results where the Fortran source code can't see them and, thus, take some liberties. That is, in Fortran, the array handling is in the language where the compiler can take liberties where a C compiler has to treat the work as ordinary code and not take the liberties. PL/I has the same advantages. Second, similarly for string handling in PL/I. In particular, commonly in C and Fortran, string handling is via subroutines that to the linkage editor look like external references that must be linked. That indirection, stack manipulation, register save, restore, etc., PL/I doesn't have to do. Scientific, engineering, analytical code is awash in multidimensional arrays, and there Fortran and PL/I can beat C. Now nearly all code is awash in string handling, and there PL/I can beat both Fortran and C. For assembler, the old remark was that in practice an assembler programmer would write his own code that was less well designed and, thus, slower than C, Fortran, and PL/I. Further, the assembler programmer would be tempted to have a large library for arrays and strings that would be external subroutine calls likely with the overhead. Sure, in principle, assembler should win, but in practice writing code good enough to win is usually a bit too much work. More generally, compiled functionality offered directly by the language can be faster than libraries of external subroutines, methods, etc.
- glangdale 7y agoHow current is this impression? There continue to be first-rate Fortran compilers that probably win on numerics for exactly the reasons you state, but I would be surprised that PL/I has a toolchain that's kept up with modern architecture. String handling, on most modern toolchains, mostly goes to inline functions or builtins that the compiler has been (rather pragmatically, if a big disgustingly) made aware of. It's very unlikely that strcpy or memmem is going through external linkage.
- maxk42 7y agoThe two biggest factors involved are (1) Runtime. Even though C has "no" runtime, there is some runtime overhead: Variable allocation / de-allocation and alignment. Function invocation / call stack setup. Et cetera. I don't know enough about Rust to understand what its exact "runtime" is like, but I'd bet it's not exactly like C. (Rust is strongly-typed, is it not? Does it do run-time type-checking?) Exception handling in particular requires a non-negligible amount of run-time overhead and may account for a significant proportion of the difference between the performance of Rust and C. (2) Optimization. Rust and C are going to be compiled with either GCC or LLVM. The way both work is they compile the program to an internal representation of opcodes, which then get optimized and platform-specific, optimized machine code is then emitted. In some cases the specific set of opcodes or optimizations used with various conventions / data structures may be more efficient or less efficient. Over time this will improve.
- atoav 7y agoAFAIK Rust type checks only during compilation and won’t allow you to cast values of one into another without clearly defining how it should work. So if there are any type related checks done during runtime, they happen because you added them.
- paulddraper 7y ago> Rust is strongly-typed, is it not? Yes, at compile-time. > Does it do run-time type-checking? No. The closest thing is the occasional vtable-based dynamic dispatch. > Exception handling in particular requires a non-negligible amount of run-time overhead Wouldn't the side-table approach be used? That has essentially zero cost, if an exception is not thrown.
- maxk42 7y agoWell again -- I don't know how Rust does it specifically, but when you compile C++ programs in g++ with exceptions disabled you pretty consistently see a 10% speed improvement right off the bat and the performance falls more closely in line with the performance of C code. Specifics aside the broader point is that the runtimes between the two languages will differ.
- jamessteel 7y agoThis is an additional comparison I found with other languages. https://github.com/ixy-languages/ixy.swift/blob/master/performance/Comparison.pdf https://github.com/ixy-languages/ixy.swift/blob/master/perfo...
- didibus 7y agoWould be nice to also see why Java is slower than C# and Go.
- masklinn 7y agohttps://www.reddit.com/r/programming/comments/d2pku3/a_highspeed_network_driver_written_in_c_rust_go_c/ezwm64t/ https://www.reddit.com/r/programming/comments/d2pku3/a_highs... > We are at ~20 bytes of allocation per forwarded packet in Java. C# and Go have much better facilities to avoid heap allocations.
- pjmlp 7y agoJava will eventually get them via Panama and Valhalla projects, something that should have been there from the the get go, though.
- ncmncm 7y agoC is the wrong language to compare to. More precisely, Rust should be able to beat C, routinely. Presumably it will, when it gets more optimizer attention. With any luck, that improvement can go into LLVM proper, and speed up many other languages besides. Why should Rust beat C? First, it does not suffer from the pointer aliasing faults C has. Second, const really means const, where in C the compiler has to assume any pointer-to-const might be use to write through. Because alias analysis is ironclad, copies can be elided even if some function borrows a reference to the copy. At a larger scale, improved abstraction tools mean more powerful libraries may be written and used, that would be unavailable to the C coder. 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. This is why articles about other languages comparing them to some fraction of C performance are amusing. C is a low bar. If you can't beat C, you are Doing It Wrong. In Rust's case, that would be "still doing it wrong"; we may assume fixing this is on the schedule, after various "getting it right" and "compiling faster" goals are met.
- codesushi42 7y agoIt 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.
- andrepd 7y agoNewbie to rust, but surely there must be a way to disable bounds checking in rust, right?? Like C++ std::vector has operator() (bounds checked) and operator[] (pointer dereference, unsafe), or other languages have get/unsafe_get (ocaml), surely rust has a way to disable bounds checking as well (or disables it for optimised builds)
- steveklabnik 7y agoYes, you use .get_unchecked instead of [].
- lmkg 7y agoAs a design choice, Rust prefers not to change the semantics of methods depending on the context, but instead to expose different methods. Arrays offer get_unchecked and get_unchecked_mut methods, which can only be called inside unsafe blocks. The one exception I'm aware of is that integer overflow panics in debug builds, and silently wraps in release builds. But in most other cases, there will be different separate methods with different semantics, requiring an unsafe context as appropriate.
- masklinn 7y ago> Newbie to rust, but surely there must be a way to disable bounds checking in rust, right?? There's an unsafe method to not do bounds-checking, keeping in mind that indexing outside the collection is UB. It's usually a better idea to e.g. use iterators, or try and nudge the optimiser towards removing the bounds checks. > disables it for optimised builds A compiler flag to add UBs to a valid (though not correct) program is not considered a great idea by the rust team.
- hamilyon2 7y agoSo, the main point I got from article is: rust has runtime array bound checks and integer owerflow checks. I am excited. Where do I sign up?
- CodesInChaos 7y agoCurrently Rust doesn't make proper use of its aliasing rules because of bugs in LLVM. This can reduce performance of code by forcing unnecessary memory accesses. As an experiment you could try the `-Zmutable-noalias=yes` option, which should enable these optimizations (but exposes you to those bugs). See [masklynn's comment](https://news.ycombinator.com/item?id=20948779 https://news.ycombinator.com/item?id=20948779). In general Rust suffers a bit because LLVM is primarily a C++ compiler backend. For example infinite loops without side-effects are UB in C++, but supposed to be well defined in Rust. This is even an issue for C, because `while(1) {}` is a valid in C but UB in C++. https://github.com/rust-lang/rust/issues/28728 https://github.com/rust-lang/rust/issues/28728
- emmericp 7y agoYeah, enabling noalias optimizations is on our todo-list. I think that’s one of the most interesting performance features of Rust. It could be the thing that makes Rust faster than C, ultimately. Too bad it‘s so broken in LLVM :(
- masklinn 7y ago> Too bad it‘s so broken in LLVM :( TBF it wasn't just LLVM: in https://github.com/rust-lang/rust/issues/54878 https://github.com/rust-lang/rust/issues/54878 "nikic" and "comex" provided C test cases which failed in both clang[0] and gcc[1]. Though GCC has since fixed the issue while it remains open on LLVM. [0] https://bugs.llvm.org/show_bug.cgi?id=39282 https://bugs.llvm.org/show_bug.cgi?id=39282 [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87609 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87609
- fulafel 7y agoThe infinite-loop Rust issue is old, and the asm "" fix had been known for a long time. I wonder what is behind this seeming perf over correctness decision.
- timvisee 7y agoWe should not forget that Rust wants safety over speed, so it utilizes array bound checks and such at runtime, because programmers make mistakes. Sadly, that introduces slight overhead.