37 ms·
Speed of Rust vs. C
- nyc_pizzadev 6y agoShouldn’t this be Rust vs C++? C++ has a lot more parallels to Rust. Both are big, complex, and safe languages that can tuned for high performance. Infact, I would like to see more comparisons of Rust and C++ in the future.
- burntsushi 6y agoNo? I mean, if you're asking whether a Rust vs C++ comparison is useful, then sure, the answer is trivially true. If you're asking whether a Rust vs C++ comparison is more useful than a Rust vs C comparison, then the answer is "maybe yes, depending." But certainly a Rust vs C comparison is useful on its own.
- pornel 6y agoAuthor here: I'm a C programmer, who's replacing C with Rust. I've never liked C++ and never felt I fully get it. I've managed to fully grasp Rust though. I don't see that much similarity between Rust and C++ other than both use angle brackets for generic code and aspire to have zero-cost abstractions. C programming patterns have more-or-less equivalents in Rust. OTOH non-trivial C++ OOP or template usage is alien and hard to adapt to Rust. Rust has 1 (one) way to initialize an object. No constructors, initializer lists, or rules-of-<insert number>. Move semantics are built-in, without move/copy constructors/NRVO/moved-out-of state. No inheritance. No object truncation. Methods are regular function pointers. No SFINAE (generics are equivalent to concepts, and dumber, e.g. no variadic). Iterators require only implementing a single method. Operator overloading is all done in the style of the spaceship operator. It's not the same kind of complexity.
- mlindner 6y agoRust replaces uses of C in many ways that C++ never could so I think the comparison is apt. There isn't extensive use of C++ in the embedded world nor is it used much for writing kernel drivers, but Rust is making big inroads into both of those arenas.
- jancsika 6y ago> alloca and C99 variable-length arrays I remember making an argument on a mailing list against using alloca on the grounds that there's usually a stack-blowing bug hiding behind it. As I revisited the few examples I remembered of it being used correctly, I strengthened my argument by finding more stack-blowing bugs hiding behind uses of alloca.
- deleted 6y ago[deleted]
- josephg 6y agoA few years ago I hand ported a skip list implementation that used inlined dynamic arrays from C to rust. (Like, the last entry of the struct was a dynamically sized Foo[];). I needed a scattering of unsafe{} blocks and a bunch of tricks to make the resulting rust code equivalent to C, in order to prevent extra allocations + memory fragmentation on the rust side. When I ran my simple fuzz test in rust it seg faulted, crashing in 'safe' code. I thought for a moment there might be something wrong with the compiler (hahaha no). Sure enough, there was a bug in one of my far-too-clever unsafe blocks that was corrupting memory. Then that was in turn causing a crash later in the program's execution. That was one of my first big "aha" moments for rust - in rust because segfaults (should be) impossible in safe code, I only needed to study the code in my ~30 lines of unsafe code to find the bug. (Compared to 150+ lines of regular code). I had some similar bugs when I wrote the C version earlier, and they took all day to track down because in C memory corruption can come from anywhere.
- jandrewrogers 6y agoAs an observation, performance optimized code is almost always effectively single-threaded these days, even when using all the cores on a CPU to very efficiently process workloads. Given this, it is not clear to me that Rust actually buys much when it comes to parallel programming for the purposes of performance. Is there another reason to focus on parallelism aside from performance? This reminds me of when I use to write supercomputing codes. Lots of programming language nerds would wonder why we didn’t use functional models to simplify concurrency and parallelism. Our code was typically old school C++ (FORTRAN was already falling out of use). The truth was that 1) the software architecture was explicitly single-threaded — some of the first modern thread-per-core designs — to maximize performance, obviating any concerns about mutability and concurrency and 2) the primary performance bottlenecks tended to be memory bandwidth, of which functional programming paradigms tend to be relatively wasteful compared to something like C++. Consequently, C++ was actually simpler and higher performance for massively parallel computation, counterintuitively.
- rational_indian 6y ago> As an observation, performance optimized code is almost always effectively single-threaded these days, even when using all the cores on a CPU to very efficiently process workloads. Why? Edit: Thanks for all the replies. It seems this applies to data-parallel workloads only. I'd use a GPU for this. An RTX 3090 has around ~10000 CUDA cores (10000 simultaneous operations) v/s just ~10 for CPUs.
- mhh__ 6y agoI think the issue is that the memory is the bottleneck in many applications (i.e. 2 loads per cycle, despite many more Functionanl units) and those workloads tend to be very non-embarassinglt parallel.
- tehjoker 6y agoSometimes your job has few or no inter-task dependencies and so there's no need to share between threads, but there's a heck of a lot of work that needs to be completed.
- vlmutolo 6y ago> "Clever" memory use is frowned upon in Rust. In C, anything goes. For example, in C I'd be tempted to reuse a buffer allocated for one purpose for another purpose later (a technique known as HEARTBLEED). This made me laugh
- viraptor 6y agoIt's not trivial to write a funny and clever burn, but this just hits the spot...
- waterhouse 6y agoThat is nice, although I think Heartbleed was due to a missing bounds check enabling the reading of adjacent memory, not due to reusing the same buffer...
- NobodyNada 6y agoIf my memory is correct: yes, the root cause was a missing bounds check, but the vulnerability was much worse than it could have been because OpenSSL tended to allocate small blocks of memory and aggressively reuse them — meaning the exploited buffer was very likely to be close in proximity to sensitive information. I don’t have time right now to research the full details, but the Wikipedia article gives a clue: > Theo de Raadt, founder and leader of the OpenBSD and OpenSSH projects, has criticized the OpenSSL developers for writing their own memory management routines and thereby, he claims, circumventing OpenBSD C standard library exploit countermeasures, saying "OpenSSL is not developed by a responsible team." Following Heartbleed's disclosure, members of the OpenBSD project forked OpenSSL into LibreSSL.
- waterhouse 6y agoAh, good point; at the very least things like zeroing out buffers upon deallocation would have helped. Yes, I was a fan of the commits showing up at opensslrampage.org. One of the highlights was when they found it would use private keys as an entropy source: https://opensslrampage.org/post/83007010531/well-even-if-time-isnt-random-your-rsa https://opensslrampage.org/post/83007010531/well-even-if-tim...
- discardable_dan 6y agoA graph would be good. Any graph. Preferably multiple. Otherwise, this is all empirical data. Show me why Rust wins, and how. Telling me "doubly-linked lists are slow" is not useful, as a developer considering one of these two languages.
- mhh__ 6y agoGraphs are empirical data, surely. All benchmarks should be delivered in the form of a graph and histogram, I had to close a PR recently where the "optimization" was 1% of a standard deviation away from the mean without even running either implementation!
- brundolf 6y agoThis isn't that type of post. Sometimes what's useful is a brain-dump of heuristics and tidbits and general impressions formed over years and years of experience. Sometimes that's more useful, or even more accurate, than hard benchmark data.
- howinteresting 6y agoMost things in life are subjective and cannot be reduced to graphs and other "empirical data". I learned this later in life than I should have, and since then I've spent time and effort building some of the mental circuits required to evaluate subjective experiences and arguments. Perhaps doing so may be useful to you as well.
- 0xdeadfeed 6y agoShow me some numbers please, or I’ll just take it as another list of wishes that Rust fans think/want to be true.
- moonchild 6y ago> C libraries typically return opaque pointers to their data structures, to hide implementation details and ensure there's only one copy of each instance of the struct. This costs heap allocations and pointer indirections. Rust's built-in privacy, unique ownership rules, and coding conventions let libraries expose their objects by value The primary reason c libraries do this is not for safety, but to maintain ABI compatibility. Rust eschews dynamic linking, which is why it doesn't bother. Common lisp, for instance, does the same thing as c, for similar reasons: the layout of structures may change, and existing code in the image has to be able to deal with it. > Rust by default can inline functions from the standard library, dependencies, and other compilation units. In C I'm sometimes reluctant to split files or use libraries, because it affects inlining This is again because c is conventionally dynamically linked, and rust statically linked. If you use LTO, cross-module inlining will happen.
- rectang 6y ago> ABI compatibility Rust provides ABI compatibility against its C ABI, and if you want you can dynamically link against that. What Rust eschews is the insane fragile ABI compatibility of C++, which is a huge pain to deal with as a user: https://community.kde.org/Policies/Binary_Compatibility_Issues_With_C%2B%2B https://community.kde.org/Policies/Binary_Compatibility_Issu... I don't think we'll ever see as comprehensive an ABI out of Rust as we get out of C++, because exposing that much incidental complexity is a bad idea. Maybe we'll get some incremental improvements over time. Or maybe C ABIs are the sweet spot.
- anfilt 6y agoRust has yet to standardize an ABI. Yes you can call or expose a function with C calling conventions. However, you cant pass all native rust types like this, and lose some semantics. However, as the parent comment you responded to you can enable LTO when compiling C. As rust is mostly always statically linked it basically always got LTO optimizations.
- johncolanduoni 6y ago
- antiquark 6y agoYeah but, C is essentially 32 years old by now. A more useful comparison would be to modern C++.
- nindalf 6y agoI think it’s a reasonable comparison. C is still a language that is widely used. In some niches, it is the only acceptable language. Comparing C with Rust is useful for people in those niches. An example of this is the Linux kernel.
- w-m 6y agoActually I t’s even older. I know it’s not an official standard, but most if not all points on C in the article would also apply to K&R C. The book was published in 1978, more than 40 years ago.
- Animats 6y ago"But the biggest potential is in ability to fearlessly parallelize majority of Rust code, even when the equivalent C code would be too risky to parallelize. In this aspect Rust is a much more mature language than C." Yes. Today, I integrated two parts of a 3D graphics program. One refreshes the screen and lets you move the viewpoint around. The other loads new objects into the scene. Until today, all the objects were loaded, then the graphics window went live. Today, I made those operations run in parallel, so the window comes up with just the sky and ground, and over the next few seconds, the scene loads, visibly, without reducing the frame rate. This took about 10 lines of code changes in Rust. It worked the first time it compiled.
- pmarin 6y agoWithout real world data "fearlessly parallelizing all the things!" is an awful idea due to all the overhead involved. The most important design decision while writing a parallel algorithm is to decide for what amount of data is not worth it.
- riquito 6y agoHe tried with few effort and noticed that for his use case the code is faster, I fail to understand this rebuttal of the parent's comment
- deleted 6y ago[deleted]
- rbanffy 6y agoThe average cellphone today has more than 4 cores. A decent desktop can deal with 16 threads on 8 cores. There is a lot of untapped parallelism readily available waiting for the right code.
- ShinTakuya 6y agoIt's not about number of available threads, the very act of scheduling tasks across multiple threads has scheduling and communication overheads, and in many situations actually ends up being slower than running it on the same thread. That said, I think the original comment was rightly pointing out how easy it was to make the change and test it, which in this case did turn out to be noticeably faster.
- brundolf 6y ago> For example, in C I'd be tempted to reuse a buffer allocated for one purpose for another purpose later (a technique known as HEARTBLEED) Pahaha
- pabs3 6y agoFTR, there are some efforts to integrate GCC & Rust: https://github.com/antoyo/rustc_codegen_gcc https://github.com/antoyo/rustc_codegen_gcc https://github.com/Rust-GCC/gccrs https://github.com/Rust-GCC/gccrs https://github.com/sapir/gcc-rust/ https://github.com/sapir/gcc-rust/
- benreesman 6y agoA comparison between Rust and modern C++ would be more interesting in my opinion. It seems that those languages are closer in the design goal space than either is to C.
- nyc_pizzadev 6y agoAgreed, came here to say the same thing. Would be interesting to see how they stack up against each other. Both are highly evolved modern languages that make pretty much the same claims.
- _a1_ 6y agoI appreciate the article, but it would be really nice if the author could add a timestamp to his blog posts. Without timestamps, it's impossible to know if any issue described in the article body still exists. I didn't read it, because it might present outdated knowledge.
- nindalf 6y agoI read it. Didn’t find any outdated information in it.
- teleforce 6y agoPlease check reply by dig1, it does contains some mis-information. It even incorrectly refer to the Heartbleed problem.
- nindalf 6y agodig1 is wrong. He uses the age old C defence of "it's not a problem with the language, it's just bad programmers programming badly". Apparently buffer reuse isn't a problem because "sane" libraries don't do it. Well, I'll believe it when we stop seeing security issues in C code bases.
- _a1_ 6y agoThe fact that my perfectly valid comment was down voted like this shows that HN has a pretty dysfunctional community. I think that is my last comment here ;)
- not_knuth 6y agoWhat a well-written and interesting piece that gets to the point! Compared to all the religious texts I've read about Rust, this is a huge breath of fresh air. Thanks for sharing! Bookmarking this.
- AndyKelley 6y ago> computed goto I did a deep dive into this topic lately when exploring whether to add a language feature to zig for this purpose. I found that, although finnicky, LLVM is able to generate the desired machine code if you give it a simple enough while loop continue expression[1]. So I think it's reasonable to not have a computed goto language feature. More details here, with lots of fun godbolt links: https://github.com/ziglang/zig/issues/8220 https://github.com/ziglang/zig/issues/8220 [1]: https://godbolt.org/z/T3v881 https://godbolt.org/z/T3v881
- tiddles 6y agoThank you for doing the research and not just mindlessly adding features other languages have :)
- anonymoushn 6y agoOh, that's great. I write interpreters off and on and I love Zig, so it's nice to hear I can get the best code gen while keeping the language small
- celeritascelery 6y agoReally cool investigation. I wonder if this applies to rust as well. As you said though, this is finicky, and if you need this optimization for performance then you don’t want to rely on compiler heuristics.
- Measter 6y agoRust's output[0] is basically the same as Zig in this case. The unsafe is needed here because it's calling extern functions. However, in this specific instance at least, this isn't as optimal as it could be. What this is basically doing is creating a jump table to find out which branch it should go down. But, because all the functions have the same signature, and each branch does the same thing, what it could have done instead is create a jump table for the function to call. At that point, all it would need to do is use the Inst's discriminant to index into the jump table. I'm not sure what it would look like in Zig, but it's not that hard to get that from Rust[1]. The drawback of doing it this way is that it now comes with the maintenance overhead of ensuring the order and length of the jump table exactly matches the enum, otherwise you get the wrong function being called, or an out-of-bounds panic. You also need to explicitly handle the End variant anyway because the called function can't return for its parent. I don't know Zig, but from what I understand it has some pretty nice code generation, so maybe that could help with keeping the array and enum in step here? [0] https://godbolt.org/z/sa6fGq https://godbolt.org/z/sa6fGq [1] https://godbolt.org/z/P3cj31 https://godbolt.org/z/P3cj31
- brwell 6y ago> "Clever" memory use is frowned upon in Rust. In C, anything goes. No, it does not. If Rust programmers don't have discipline in C, other people have. And don't drag out some random CVE numbers again. These are about a fraction of existing C projects, many of them were started 1980-2000. It is an entirely different story if a project is started with sanitizers, Valgrind and best practices. I'm not against Rust, except that they managed to take OCaml syntax and make it significantly worse. It's just ugly and looks like design by committee. But the evangelism is exhausting. I also wonder why corporations are pushing Rust. Is it another method to take over C projects that they haven't assimilated yet?
- mfru 6y ago> It is an entirely different story if a project is started with sanitizers, Valgrind and best practices. What are the agreed upon tools and best practices in the C community as of right now?
- gridspy 6y ago> The evangelism is exhausting. My best guess is that people who are "stuck" working in C or C++ wish they could use Rust at their Jobs. Or that others would make the leap and get over the learning curve.
- pjmlp 6y agoNot until it reaches the same level as Visual Studio, Android Studio, QtCreator, XCode, CUDA and SYSCL tooling for graphical applications and GPGPU. For anything else managed languages are a much more productive option, other than writing kernel and drivers.
- c-cube 6y agoWe get that you don't like rust. But it seems like a lot of people currently using C or C++ while like to use rust at work, and might disagree about the benefits of the language and tooling. I personally know a few friends in distinct domains who work on established C++ codebases and are in this situation. There are also a lot of people who do not use C or C++, but use a bit of rust because it's so much easier to write fast little tools with it. I'm in this category. I even use threads sometimes, and it's reasonably easy. A crop of new unixy tools in rust seems to indicate other people also think alike.
- known 6y agohttps://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/rust.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... shows C is generally better
- dthul 6y agoLinking to a page that shows that the Rust version is faster than the C version in almost every case?
- gattr 6y agoTo practise Rust, I rewrote my small C99 library in it [1]. Performance is more or less the same, I only had to use unchecked array access in one small hot loop (details in README.md). I haven't ported multithreading yet, but I expect Rust's Rayon parallel iterators will likewise be comparable to OpenMP. [1] https://github.com/GreatAttractor/libskry_r https://github.com/GreatAttractor/libskry_r
- up2isomorphism 6y agoYour C library does not check malloc returns and also malloc and free everywhere inside library functions are not the best way to write a C library.
- gattr 6y agoAs for malloc/free, I'm guessing the recommendation is to allow the user to pass their own allocator on library initialization? Non-checked malloc returns - ouch, I count 12 (out of 56) without a check. Thanks for pointing this out.
- 0xdeadfeed 6y ago> While C is good for writing minimal code on byte-by-byte pointer-by-pointer level, Billions of cars with multi-billion ECUs, practically every device running an OS, and several NASA rovers disagree.
- cjohansson 6y agoHuman-friendlyness and bug-prevention is very important, of course everthing in Rust can be created in C or Assembler och in machine-code but the question is how feasible is it that a typical human can do it? Rust has a lot of potential I think
- oblio 6y agoYeah, the sooner we move away from cowboy/Hero coding, the better. We could use a bit of humility in our field.
- Aissen 6y ago> Rust can't count on OSes having Rust's standard library built-in, so Rust executables bundle bits of the Rust's standard library (300KB or more). Fortunately, it's a one-time overhead. No, it's not, especially if you have multiple binaries. There are hacks, like using a multi-call single binary, (forget about file-based privilege separation), or using an unmaintained fork of cargo to build a rust toolchain capable of dynamic linking libstd. See: https://users.rust-lang.org/t/link-the-rust-standard-library-dynamically/29175/4 https://users.rust-lang.org/t/link-the-rust-standard-library... and https://github.com/johnthagen/min-sized-rust https://github.com/johnthagen/min-sized-rust I'd be interested in any up-to-date trick to do better than this.
- ackxolotl 6y agoWe've implemented network drivers in C and Rust and did a performance comparison. Interestingly, the C-to-Rust-transpiled code ended up being faster than the original C implementation: https://github.com/ixy-languages/ixy-languages/blob/master/Rust-vs-C-performance.md https://github.com/ixy-languages/ixy-languages/blob/master/R...
- jmacjmac 6y agohttps://github.com/emmericp/ixy/blob/0e00605be4153b06df06184b5dc0401113c2c7fb/CMakeLists.txt#L6 https://github.com/emmericp/ixy/blob/0e00605be4153b06df06184... Looks like you're compiling C code with -O2. Does Rust build set -O3 on clang? Did you try -O3 with C? I know it's not guaranteed to be faster, just curious.
- Shadonototro 6y agoGood catch
- dralley 6y agoIt looks like the answer is "yes" https://doc.rust-lang.org/cargo/reference/profiles.html#release https://doc.rust-lang.org/cargo/reference/profiles.html#rele...
- jmacjmac 6y agoThen a fair benchmark would be compiling C code with clang -O3 :)
- 12thwonder 6y agoI prefer to have great ideas in rust ported over to C instead of rewriting everything with Rust. this approach will benefit all the existing softwares written in C which I think is much larger than Rust in terms of both impact and code size. am I a minority having this opinion?
- Jweb_Guru 6y agoI think it would be basically impossible to perform this task without making the language fundamentally not C. Zig is an interesting take in that direction (learn from the last 30 years but still try to be "C") that I think gets a lot closer to the ideal than most other alternatives. C++, OTOH, you could probably port most of Rust's concepts into (with some extra language changes for various reasons I don't want to get into). However, since almost no existing C++ code would typecheck in the "safe" subset without modifications, it would effectively be a different language anyway. And to be clear, this isn't necessarily because people are routinely doing dangerous stuff in C++ -- the whole Rust ecosystem has grown up around the borrow checker, which means some very basic things people use in most other languages aren't done. Here are some examples of things typical Rust code does differently from typical C++ code due to it making it much harder to perform safety checks, beyond the obvious aspect of lifetime annotations and genuinely unsafe patterns like accessing globals (sorry, it just is): * far less use of accessors, especially mutable ones (because Rust can't track split field ownership) * Rust tends to split up big "shared context" structures depending on function use, rather than logical relationships, for much the same reason (Rust conservatively assumes that all fields are used when a context object gets passed to a function as long as any pointer to the structure remains, even if the fields you use aren't being accessed). * Rust almost never uses internal or cyclic pointers. It's safe to do it with boxed data or data that doesn't move, and there are safe type mechanisms around that, but it's cumbersome since it has to be visible to the typechecker, so people usually don't bother. * single-threaded mutation through multiple pointers into the same data structure, which may even be aliased. Again, often safe (though not always), and in the safe cases there are generally safe types to enable it in Rust, but since it's not the default and requires pre-planning for all but the simplest cases, people usually don't bother. * Rust types are always annotated with thread safety information. This is usually done by default, but if it weren't it would be a huge amount of boilerplate. The reason this works is that in the cases where people are doing unsafe stuff, the type system automatically opts out and requires them to opt in. Libraries have been built around this assumption. Even if we were to port such a mechanism over to C++, the lack of these explicit annotations would mean that in practice it just wouldn't work that well--you would have to do a very detailed thread safety analysis of basically any existing library to try to assign types. Often, complying with these kinds of rules is what people coming to Rust struggle with--not so much local lifetime issues which the compiler can usually figure out, but how to structure the entire program to make life easy for the borrow checker. However, complying comes with a big benefit--it allows safety analysis to proceed purely locally in almost all cases. The reason that static analyzers don't just "do what Rust does" is that they're dealing with programs that aren't structured that way and need to perform far more global analysis to catch most of the interesting memory safety bugs that pop up in mature C++ codebases, especially the ones that evade code review. So--do I think it would be great to port this stuff over to C++ (or C, hypothetically?). Absolutely--I still prefer Rust as a language, but at the end of the day memory safety you could layer on top of existing C code would be a huge win for everyone. But I don't see it happening because of the fact that Rust's solution requires serious code restructuring. if people are going to have to rewrite their old programs anyway to work with a tractable static analysis, and not be able to use almost any existing libraries, it's not clear how much more benefit they'd have from using this subset than from just switching to Rust.
- dig1 6y ago> For example, in C I'd be tempted to reuse a buffer allocated for one purpose for another purpose later (a technique known as HEARTBLEED). You can do that in Java (with byte arrays) or in Common Lisp, so what is the point here? It is not practice in Java, Lisp nor in C and C++. > It's convenient to have fixed-size buffers for variable-size data (e.g. PATH_MAX) to avoid (re)allocation of growing buffers This is because OS/Kernel/filesystem guarantee path max size. > Idiomatic Rust still gives a lot control over memory allocation, and can do basics like memory pools, ... but in general it steers users towards "boring" use or memory. The same is done by sane C libraries (e.g. glib). > Every operating system ships some built-in standard C library that is ~30MB of code that C executables get for "free", e.g. a "Hello World" C executable can't actually print anything, it only calls the printf shipped with the OS. printf is not shipped with the OS, but with libc runtime. It doesn't have to be runtime (author needs to learn why this libc runtime is shared library and not the usually statically linked library) and you can use minimal implementations (musl) if you want static binaries with minimal size. So you are saying Rust doesn't call (g)libc at all and directly invoke kernel interrupts? Sure, you can avoid this print "overhead" in C with 3-4 lines of inline assembly, but, why? > Rust by default can inline functions from the standard library, dependencies, and other compilation units. So do C compiler. > In C I'm sometimes reluctant to split files or use libraries, because it affects inlining and requires micromanagement of headers and symbol visibility. Functions doesn't have to be in headers to be inlined. > C libraries typically return opaque pointers to their data structures, to hide implementation details and ensure there's only one copy of each instance of the struct. This costs heap allocations and pointer indirections. Rust's built-in privacy, unique ownership rules, and coding conventions let libraries expose their objects by value, so that library users decide whether to put them on the heap or on the stack. Objects on the stack can can be optimized very aggressively, and even optimized out entirely. WTF? Stopped reading after this. I find this post a random nonsense and I'd urge author to read some serious C book.
- ben0x539 6y agoAny book you'd recommend to back up your claims?
- 6y ago
- mratsim 6y ago> There are other kinds of concurrency bugs, such as poor use of locking primitives causing higher-level logical race conditions or deadlocks, and Rust can't eliminate them, but they're usually easier to diagnose and fix. Which is why so many people are creating formal verification languages and spending years in research to fix those ... That just isn't true. It's a very complex problem that is an issue in both hardware (cache-coherency protocols) to OS (atomics locks) to higher level construct (commit-rollback in databases). Consequently > But the biggest potential is in ability to fearlessly parallelize majority of Rust code, even when the equivalent C code would be too risky to parallelize. In this aspect Rust is a much more mature language than C. This couldn't be more wrong either. Rust doesn't help you write synchronization primitives safely because it doesn't handle synchronization like locks, condition variables or atomics. You need formal verification to be fearless.
- zesterer 6y agoYou've just taken the word 'fearless', a word that's clearly subjective, and said that the definition the author gives of it "couldn't be more wrong". That's... a choice.
- mratsim 6y agoThe word is misrepresenting the problem of synchronization and reducing to only memory safety. If it was that simple, Tokio wouldn't need to formally verify their implementation with an external tool and it wouldn't have found dozens of well hidden bugs.
- ben0x539 6y agoRust may or may not help you write synchronization primitives safely, but it for sure helps you use synchronization primitives without having to worry about memory safety. If you aren't parallelizing particularly subtle shenanigans, that's plenty for fearlessness.
- mratsim 6y agoComing up with a new threadsafe queue design is worthy of a paper even though it's just enqueueing and dequeueing items. Memory safety is just a small part and is a much easier problem than ensuring the absence of race conditions.
- zesterer 6y agoCode 'bloat' is a bizarre metric to use for anything unless you're on a platform with incredibly constrained executable memory like an embedded device. The fact that Rust specialises its generic code according to the type it's used with it not some inherent disadvantage of generics. That's what they're supposed to do. By choosing to not specialise, you're actively making the decision to make your code slower. Rust has mechanisms for avoiding generic specialisation. They're called trait objects and they work brilliantly. When you use void* in your data structures in C, you're not winning anything when compared to Rust. You're just producing slower code that mimics the behaviour of Rust's trait objects, but more dangerously. Code 'bloat' (otherwise known as 'specialising your code correctly to make it run faster') is not a reason to not use Rust in 2021, so please stop pretending that it is.
- Tuna-Fish 6y agoIt's not that simple. While fully specializing everything wins microbenchmarks, as C++ has shown time and time again, it can easily lose performance in large applications. If fully specializing code saves a few branches in the hot loop, but also blows through all the L1i, it can easily be a huge net negative. > Rust has mechanisms for avoiding generic specialisation. They're called trait objects and they work brilliantly. As someone who uses a lot of rust, they are sort of the red-headed stepchild. As a minimum to make the properly usable, we need a way of passing one object with multiple different traits.
- zesterer 6y ago> As a minimum to make the properly usable, we need a way of passing one object with multiple different traits. Supertraits?
- Tuna-Fish 6y agoThat is possible, but gets really hairy if you use a lot of trait objects.
- loeg 6y ago> As someone who uses a lot of rust, they are sort of the red-headed stepchild. As a minimum to make the properly usable, we need a way of passing one object with multiple different traits. What do you mean? fn foo<T: TraitA + TraitB>(x: T) { T.something(); }
- simias 6y agoI completely agree with the points made here, it matches my experience as a C coder who went all-in on Rust. >"Clever" memory use is frowned upon in Rust. In C, anything goes. For example, in C I'd be tempted to reuse a buffer allocated for one purpose for another purpose later (a technique known as HEARTBLEED). Ha! >It's convenient to have fixed-size buffers for variable-size data (e.g. PATH_MAX) to avoid (re)allocation of growing buffers. Idiomatic Rust still gives a lot control over memory allocation, and can do basics like memory pools, combining multiple allocations into one, preallocating space, etc., but in general it steers users towards "boring" use or memory. Since I write a lot of memory-constrained embedded code this actually annoyed me a bit with Rust, but then I discovered the smallvec crate: https://docs.rs/smallvec/1.5.0/smallvec/ https://docs.rs/smallvec/1.5.0/smallvec/ Basically with it you can give your vectors a static (not on the heap) size, and it will automatically reallocate on the heap if it grows beyond that bound. It's the best of both world in my opinion: it lets you remove a whole lot of small useless allocs but you still have all the convenience and API of a normal Vec. It might also help slightly with performance by removing useless indirections. Unfortunately this doesn't help with Strings since they're a distinct type. There is a smallstring crate which uses the same optimization technique but it hasn't been updated in 4 years so I haven't dared use it.
- totalperspectiv 6y agoI’ve been using smartstrings, which is both excellent and maintained. https://github.com/bodil/smartstring https://github.com/bodil/smartstring
- simias 6y agoAh, nice, I was looking at the smallstring package that's appears abandoned. I'll be sure to check this one out. The good thing about having a decent type system is that I expect that transitioning to smartstrings should be painless! Thank you for that.
- simias 6y agoIn case somebody stumbles upon this conversation in the future: I just migrated a project to use smartstrings and it works a bit differently from smallvec. Smallvec lets you decide how big you want to make the static buffer before it allocates, whereas smartstring's static buffer size is alsways `size_of::<String>() - 1`, that is 23 bytes on 64 bit architectures and 11 on 32 bits. If I want, say, a static 128B string smartstring won't do any better than std::string::String. It's still a very nice lib though, and a smart optimization, but it doesn't cover all of my use cases for small string buffers.
- planetis 6y agoIts just amusing, in this thread everyone with critical thinking and skeptical is down voted, even if one expresses himself moderately. It shows how much of zealots, Rust fanboys have become.
- ben0x539 6y agoHm, is there some specific criticism of Rust you'd like to see discussed more? It's easy to get side-tracked in these "actually my language is better than your language" with everybody launching whole broadsides of arguments, so I wouldn't be surprised if some more subtle points get lost.
- planetis 6y agoeh, I don't partake in the usual "actually my language is better than your language" that comes up in all posts. I just don't like people going overboard with their claims, when they try to promote any PL really, and would appreciate more fact checking.
- jedisct1 6y agoThis is the case every time there's a post about Rust.
- fractionalhare 6y agoCan you point to some specific comments like this? None of the top threads seem to show this, as of this writing it's mostly about thread versus process parallelism and which kinds of conditions require unsafe.
- deleted 6y ago[deleted]
- nindalf 6y agoThe problem is that most people criticising Rust don't make the case very well. If you want to read good critique, I'd recommend this - https://matklad.github.io/2020/09/20/why-not-rust.html https://matklad.github.io/2020/09/20/why-not-rust.html. This post up-to-date, succinct and objective. And most pertinently, this critique was written by someone who genuinely loves programming in Rust. Shows you that Rust users aren't blinded to the faults of the language. You shouldn't think that Rust users are fanboys just because you see push back to low effort, low knowledge critiques.
- skohan 6y ago> Both are "portable assemblers" I don't tend to think of Rust as "portable assembly", and this is indeed one of the points where I think it differs the most from C. I think of "portable assembly" as being applicable to C, because it is some version of a "minimal" level of abstraction for a high-level language. Rust is very much a tool for abstraction, and one of the USPs of rust is that the compiler abstracts away the low-level details of memory management in a way which is not as costly as other automatic memory management strategies. Maybe it's due to lack of experience, but with C code it's fairly easy to look at a block of code and imagine approximately which assembly would be generated. With highly abstract Rust code, like with template-heavy C++ code, I don't feel like that at all.
- pornel 6y agoWith a bit of experience you get the same in Rust. Rust does not abstract away memory management. For example, it never heap allocates anything implicitly. It inserts destructors, but does so predictably at end of scopes, in a specified order. Rust heavily uses iterators with closures, but these get aggressively inlined, and you can rely on them optimizing down to a basic loop. For code generation they're not too different from a fancy C macro. And if in doubt, there's https://rust.godbolt.org/ https://rust.godbolt.org/ (don't forget to add -O to flags)
- Shadonototro 6y agoVery biased comparison without actual source or numbers to back things Even more surprising it got to front page Do people really have low standard of quality on hacker news too?
- 0xdeadfeed 6y agoIt's not really surprising TBH. Everyone knows HN is a pro-Rust echo chamber.
- ReactiveJelly 6y agoI'm a Rust evangelist, but the article is titled "Speed of Rust vs. C" and doesn't seem to contain even one benchmark. For fuck's sake.
- tazjin 6y agoIn my opinion, the level of detail in this article is much more useful than small benchmarks of code that doesn't resemble real applications anyways.
- zesterer 6y agoBenchmarks wouldn't tell the whole story. This detailed writeup is far better in that it gives information about how and where the two languages differ.
- pornel 6y agoThere's already The Benchmarks Game and ixy-languages if you want hard numbers. Maximum speeds are already explored. I wanted to discuss an aspect that's not typically covered by pure benchmarks: what can you expect from normal day-to-day use of these languages. Not fine-tuned hot loops, but a "median" you can expect when you just need to get shit done. If I tried to write a benchmark code to represent average, practical, idiomatic, but less-than-maximally optimized code, I don't think anyone would believe me that's a fair comparison. So I describe problems and patterns instead, and leave it to readers to judge how much applies to their problems and programming style.
- howinteresting 6y agoThank you for writing this. Real world, qualitative experience reports are vital. My experience using Rust vs C aligns with yours as well.
- igouy 6y ago> Maximum speeds are already explored. Also sub-maximum speeds — start at the bottom of the measurements and work up from the 5.37s g++ program to the 0.72s g++ program :-) https://benchmarksgame-team.pages.debian.net/benchmarksgame/performance/spectralnorm.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
- johndoe42377 6y agoFirst non-bullshit article about Rust for long time.
- eqvinox 6y agoIs it possible to do RCU in Rust? Without unsafe blocks?
- steveklabnik 6y agoI don't know all of the subtleties, but it sounds like https://doc.rust-lang.org/stable/std/sync/struct.RwLock.html https://doc.rust-lang.org/stable/std/sync/struct.RwLock.html to me? At least in some way?
- nyanpasu64 6y ago@steveklabnik, RCU is different from RwLock in that the single writer and all readers never block each other. Given that RCU is a complex wait-free data structure (though I don't fully understand it), I suspect it may not necessarily be possible to implement it without unsafe blocks, purely in terms of the standard library concurrency types (atomics and Arc can be used without unsafe, but themselves contain unsafe blocks). The general goal is to create an abstraction which encapsulates unsafe blocks such that it's impossible for outside users calling safe functions to violate memory safety. Of course, libraries sometimes have bugs that need to be fixed.
- steveklabnik 6y agoAh yeah, makes sense. I would also imagine it needs unsafe, yeah.
- up2isomorphism 6y agoMy experience is that languages survives not because of a particular feature, but because they are USEFUL in practice to produce a software. The fact that C is used in so many places speaks for itself about it usefulness. And this is done by writing software by majority of C programmers instead of jumping on every forum to attack other languages, writing extended blog posts just to convince people that they "should" switch to the language they like. Also if you believe bounds check is the most difficult thing in software development, it just mean that you haven't dealt with a sufficient system yet or you just pretends to be. The similar thing also applied to that if you think naively putting pthread_mutex_lock and unlock around the data structure is hard, it just means you haven't touched the scenarios that C programmers resorts to non-trivial locking mechanisms for.
- myrrlyn 6y agolol
- BatmanAoD 6y agoNothing in this article seems to be saying that C "isn't useful". It also doesn't state that bounds checks are the "most difficult thing in software development." As the article mentions, C is 50 years old. The fact that it's still used is evidence of its usefulness, sure. It has outlasted almost all of its peers. Rust has been stable for under 6 years. In that time, it's been adopted by a slew of major companies, and people have used their free time to write some extremely good software in it. So by that metric, Rust's usefulness speaks for itself, too.
- up2isomorphism 6y agoThe article is using one or two features in a quick marketing style to promote rust. - Regardless it is true or not, this seldom works in long term. I just simply point this observation out. In fact language as tool is never about more features, it is about minimum features for maximize utilities, and Rust is already on the domain of "feature-rich" language.
- up2isomorphism 6y agoThe article talks way too high level and is written like a marketing people even the title sounds technical, for example: "Rust enforces thread-safety of all code and data, even in 3rd party libraries, even if authors of that code didn't pay attention to thread safety. Everything either upholds specific thread-safety guarantees, or won't be allowed to be used across threads."
- burntsushi 6y agoThat doesn't sound too high level to me. Maybe a small quibble is the definition of "thread safety," but a reasonable one would be, "no undefined behavior in the presence of simultaneous access." In other words, no data races. And that's absolutely true and consistent with Rust's definition of safety. Another small quibble might be that, "even if the authors of that code didn't pay attention to thread safety and didn't use 'unsafe'" would be more precise. It's not marketing speak.
- up2isomorphism 6y agoThere is simply no way you can enforce "thread safety on ALL data", unless you pay unreasonable amount of synchronization costs, which in that case, is a trivial thing to accomplish. This is as same as some one tell you that you will never loose any money by investing a certain asset.
- steveklabnik 6y agoHow well do you know Rust, and how it works, and what it guarantees? Like, do you have a specific objection to the way Rust accomplishes this?
- up2isomorphism 6y ago"thread safety for all data" is not even a well-defined term, I do not know what you are trying to argue about.
- hsaliak 6y agoFor parallelism, Modern tooling like TSAN can close the gap somewhat. If you are planning to introduce threads, not testing it with TSAN is silly at best.
- howinteresting 6y agoIf you're writing safe, parallel Rust code, you don't really need to use TSAN. You may hit a deadlock sometimes, but those tend to be easy to figure out in my experience. The people implementing the libraries you use (e.g. Rayon) may have to use TSAN, of course.
- hsaliak 6y agoFor sure - I was mentioning TSAN in the context of threaded C code
- docmars 6y agoAwesome, now do charts! ;)
- jblow 6y agoThis entire article is nonsense. To a first approximation, the speed of your program in 2021 is determined by locality of memory access and overhead with regard to allocation and deallocation. C allows you to do bulk memory operations, Rust does not (unless you turn off the things about Rust that everyone says are good). Thus C is tremendously faster. There is this habit in both academia and industry where people say "as fast as C" and justify this by comparing to a tremendously slow C program, but don't even know they are doing it. It's the blind leading the blind. The question you should be asking yourself is, "If all these claims I keep seeing about X being as fast as Y are true, then why does software keep getting slower over time?" (If you don't get what I am saying here, it might help to know that performance programmers consider malloc to be tremendously slow and don't use it except at startup or in cases when it is amortized by a factor of 1000 or more).
- zozbot234 6y ago> (If you don't get what I am saying here, it might help to know that performance programmers consider malloc to be tremendously slow and don't use it except at startup or in cases when it is amortized by a factor of 1000 or more). Rust is now getting support for custom local allocators ala C++, including in default core types like Box<>, Vec<> and HashMap<>. It's an unstable feature, hence not yet part of stable Rust but it's absolutely being worked on.
- jblow 6y agoSure, but you are still going to be constrained greatly in terms of what those allocators are able to do, are you not?
- steveklabnik 6y agoIn what way? What kind of constraints are you imagining here?
- jblow 6y agoI guess I am confused by the question. The job of the borrow checker is to constrain what you are allowed to do, and it's well-understood that it constrains you to a subset of correct programs, so that you stay in a realm that is analyzable.
- SimpleGoto1080 6y agoWith the lack of context in this article I'm willing to bet rust isn't actually faster than C
- bambam24 6y agoRust is low-level enough that if necessary, it can be optimized for maximum performance just as well as C. Higher-level abstractions, easy memory management, and abundance of available libraries tend to make Rust programs have more code, do more, and if left unchecked, can add up to bloat. However, Rust programs also optimize quite well, sometimes better than C. While C is good for writing minimal code on byte-by-byte pointer-by-pointer level, Rust has powerful features for efficiently combining multiple functions or even whole libraries together. But the biggest potential is in ability to fearlessly parallelize majority of Rust code, even when the equivalent C code would be too risky to parallelize. In this aspect Rust is a much more mature language than C.