Performance of Rust Language [pdf]

(github.com)

30 points | by tanelpoder 4 hours ago

5 comments

  • jandrewrogers 28 minutes ago
    I would summarize it thusly: Rust is roughly as performant as C. This matches my experience and Rust is more ergonomic than C in many regards. The caveat is that modern C++ is notably more performant than C and by implication Rust. This also matches my experience for both C and Rust.

    I think most of this is attributable to the ergonomics of compile-time expressiveness. C++ can effortlessly do things that require mountains of ugly boilerplate and macros in C or Rust. In principle they can express the same things but no one wants to write or deal with that ugly boilerplate so the equivalency is never realized in real code bases.

    Zig is interesting because it slots in as a C-like language with a competent and expressive compile-time story. I don’t use Zig but I recognize its game.

    • afdbcreid 23 minutes ago
      Is C++ more performant than C? I find this hard to believe. C++ does not have any construct that cannot be replicated, or is not common, in C. The only candidate is using virtualization and void* pointers instead of monomorphized generics which some C code does for the lack of better options, but that's not a problem in Rust as well.

      If anything, Rust has the potential to be more performant than C due to its aliasing rules (C has `restrict` but it's rarely used, standard C++ does not have even that). The current perf stats show it does make Rust code faster but just a little bit, although we don't utilize the full optimization potential currently (LLVM does not do many possible optimizations here, and `noalias` is weaker than Rust's aliasing rules). It can also affect autovectorization, and if it does the effect could be dramatic.

      • loeg 11 minutes ago
        C++ you get templated generic algorithms that in practice no one really does with C because macros suck too much. So in C typically you'd have a runtime generic routine that doesn't inline. A classic example here is qsort() vs std::sort().
        • afdbcreid 1 minute ago
          I explicitly acknowledged that:

          > The only candidate is using virtualization and void* pointers instead of monomorphized generics which some C code does for the lack of better options, but that's not a problem in Rust as well.

          But in fact, if speed is a concern to you, even in C you will use "templated" sorting (via macros or code generation).

      • fithisux 6 minutes ago
        You can write C style C++ and enjoy the same benefits.

        In Twitter a user explained me that it is common in embedded space.

        You do not need the OOP, RTTI, exceptions.

        Like C with most use cases of preprocessor replaced by generic programming.

        • afdbcreid 1 minute ago
          So? How is that an argument that C++ is more performant than C? It's only an argument that it's not less performant.
  • gobdovan 1 hour ago
    Rust is in an awkward position of being already complicated enough that adding proofs for skipping bounds checks probably will not happen for a long time, even though this kind of low-level operation is where a lot of optimisation is lost.

    Compounding on this, Rust is also unstable underneath, since there is no public, stable contract for carrying high-level semantics from HIR into MIR. Because these high-level invariants are lost during compilation, the compiler cannot easily use them to prove and eliminate low-level safety checks. But even if the frontend was perfect, Rust relies on LLVM's language-neutral SCEV, which operates purely on low-level math and cannot reason about high-level language semantics.

    Ultimately, a lot of things would need to change for Rust to pay no performance for safety features.

    • aw1621107 10 minutes ago
      > Compounding on this, Rust is also unstable underneath, since there is no public, stable contract for carrying high-level semantics from HIR into MIR. Because these high-level invariants are lost during compilation

      Not sure if I'm just out of the loop, but I'm having a hard time following this line of reasoning. Why is a public and/or stable contract needed to carry high-level semantics from HIR to MIR? Neither seems necessary to me; from what I understand HIR and MIR are rustc-internal so public contracts shouldn't matter, and the lack of stability means the Rust devs aren't precluded by backwards compatibility from modifying the IRs to add the ability to carry such invariants.

    • afdbcreid 28 minutes ago
      The overhead of bounds checking varies a lot. In the common case it's negligible (few percents), but in some cases, depending on what you build, it can go up to even 20%. And if it prevents autovectorization it can cost even more.

      There are techniques to minimize the perf loss, though (safely), and of course you can use unsafe code. If you do it smartly, in the vast majority of cases bound checks do not matter (in fact, even in C++ there is a push for a hardened standard library that does bound checks, and e.g. Google uses that).

      Rust will never include full proofs, but it might include ranged integers which can minimize bound checks even more.

  • Animats 56 minutes ago
    There's a discussion of "delayed bounds checking", but not "hoisted bounds checking", where bounds checking is done early. Consider

        let mut tab: [usize;100] = [0;100];
        ...
        for i in 0..101 {
            tab[i] = i;
        }
    
    This must panic at i=100. Panic becomes inevitable at entry to the loop. Is the compiler entitled to generate a check that will panic at loop entry? The slides suggest that Rust does not hoist such checks, and, so, with nested loops, it has trouble getting checks out of the loop, which prevents vectorization.
    • afdbcreid 31 minutes ago
      Currently LLVM cannot do that because the panic message includes the erroneous index. You can do it manually though if you add `_ = tab[100]`.

      Even if the panic message would not include the index, LLVM was unable to do that if the previous iterations had side effects (for example if `tab` is not a local variable).

  • _alphageek 23 minutes ago
    I would have liked to have the checks-off delta plotted across rustc versions - the deck notes this stuff moves non-monotonically, so a trend line would say more than a single-version snapshot.
  • encodedrose 2 hours ago
    If I followed, Rust's memory safety guarantee means sacrificing roughly ~3% performance with some worst case paths being ~15% (compared to C++ performance)?
    • marcosdumay 1 hour ago
      That's on the typical performance for bounds checking in C too.

      But no, "memory safety" includes most of the things discussed on the slides, and those number are for bounds checking only.