4 ms·
OT but I wonder why Go (which I'm using since some years after getting away from Rust) is consistently much slower than Rust, even with PGO.
by KingOfCoders 3y ago
OT but I wonder why Go (which I'm using since some years after getting away from Rust) is consistently much slower than Rust, even with PGO.
- 0dayz 3y agoWhile I haven't looked into it too much wouldn't it be because of the fact that it uses garbage collection?
- t8sr 3y agoYou can profile your Go programs and see how much time they spend in the garbage collector. If it's over 20% you probably have a bug. And of course Rust and C also spend time on memory management, it just has the effect of making everything a little slower rather than having one big ticket item in the profiler. Go is slower than C and Rust, IME, for two big reasons: 1) It puts a lot of stuff on the heap, that could've been on the stack. This wrecks locality and makes Go call malloc/free a lot. (Actually not the malloc, just a malloc.) 2) Rust and C++ offer fine control over things like dispatch, checked and unchecked casts, control for allocation, strings, etc. Go usually offers one unified thing that's simple to use. So a lot more calls are virtual, a lot more things are checked at runtime, etc. I don't understand why garbage collection is so mythologized. TBH I don't even understand why people think `Arc` and `shared_pointer` aren't garbage collection. Is this just the effect of Swift and Rust's marketing ("look, ma, no GC")?
- coldtea 3y ago>I don't understand why garbage collection is so mythologized. TBH I don't even understand why people think `Arc` and `shared_pointer` aren't garbage collection. Because when people talk about garbage collection in the context of the dichotomy between Rust and Java or Go, they talk about the latter being automatic memory management based on various heuristics, with the related runtime overhead, non-deterministic, etc. Whereas Rust Arc is just reference counting and explicit, not to mention enforced at compile time. I don't understand how and why anyone would consider the two as the same thing. They are both GC in the same sense that cars and boats are both vehicles.
- lomase 3y agoDo you really don't see how a GC and reference counting are similar?
- coldtea 3y agoYes: in the same way a boat and a car are similar since they both "take you from A to B". Now, do you really don't see how a Java/GO style GC and reference counting are different? It's not like I didn't enumerate some characteristics that make all the difference - and that explain why people do not think of those two cases as just a single thing called "garbage collection", and also why people believe that ARC would have less overhead than a Java/GO style garbage collector.
- pjmlp 3y agoThere isn't a Java style GC, there are multiple implementations, including with reference counting GC algorithms. The language and runtime specification don't say anything about GC algorithms required by compliant implementations.
- t8sr 3y agoSorry, but it really sounds to me like you're repeating something from documentation or CS 101. Have you ever actually worked on a garbage collector? * ARC is not deterministic outside of toy examples * The runtime overhead of modern GC algorithms has a lower bound than ARC, and this has been proven formally * Reference counting is usually atomic, so it implies other wonderfully expensive things like CPU cache flushes, memory fences, etc. * The runtime overhead of deferred GC also happens outside of the hot path, and doesn't do unpredictable non-local things if you release a reference at an inconvenient moment * ARC is in fact a GC strategy. E.g. obj-c has autorelease pools that postpone deletion until later, so the hot path doesn't get interrupted if reference count of something reaches zero early. These things are a spectrum. Rust leans a lot on the borrow checker, and both Rust and C++ nudge you toward using fewer heap small allocations because it is inconvenient to use them. This is where a lot of the performance wins come from, not because reference counting somehow shaves off 20% of the program's runtime. In fact, ARC is not only a GC strategy, it is close to being the worst possible GC strategy.
- KingOfCoders 3y agoHeard the heap argument a lot, but until your "This wrecks locality and makes Go call malloc" it didn't make sense to me, thank you!
- lifthrasiir 3y agoGo strongly favors a fast compilation over performance gain, which influenced both the language design and implementation. For example, Daniel Lemire pointed out that its inlining strategy is very conservative [1]; inlining unlocks tons of optimization possibilities but also can dramatically affect the compilation time, which is probably why Go did so. [1] https://lemire.me/blog/2020/06/04/the-go-compiler-needs-to-be-smarter/ https://lemire.me/blog/2020/06/04/the-go-compiler-needs-to-b...
- KingOfCoders 3y agoYes, I know, but then C had optimization levels for compilers for decades. Go could compile fast for development and compile slow for production. But as I've said, even with PGO there is not a lot of gain compared to Rust.
- t8sr 3y agoIn addition to what the other post said, Go also favors simplicity for the programmer. So stack and heap are "unified", a lot of dispatch is virtual and you rely on the compiler optimizing away all the dynamisms and checks. As you might imagine, the compiler has been getting better, but these are hard problems. If you are willing to spend time on it, you can improve Go's performance quite a lot by finding places where heap escape analysis failed, and trying to fix them. But this is extremely frustrating to do. Another thing is interfaces and all the runtime checks Go likes to do. In C++ parlance, every interface-valued dispatch is virtual by default. For many, Go is "fast enough". Usually it's within a factor of 2 of Rust, which puts it far ahead of the interpreted languages it's usually replacing.
- spoiler 3y ago> Go also favors simplicity for the programmer I used to say this too, but after I've been writing Rust for a few years, in hindsight it feels like Go as a language is simpler, but I tend to end up with more code to maintain. I guess the error handling leaves a bit to be desired for, and I'd say even though it's better than exceptions, I still prefer Result in Rust. I hope that with the introduction of generics in Go, things will become simpler (I know a lot of people dislike this type of polymorphism because it feels like extra complexity, but I've always seen it as a tool to reduce complexity)
- t8sr 3y agoI think the amount of code you need to maintain and simplicity are orthogonal things. When we say go is simple, what we usually mean is that by looking at a bit of code, it's obvious what it does. There's no way that `x + y` can be anything other than addition, or `f(x)` anything other than a function call. It also means, that only function calls can hide additional complexity. You could also talk about lexical proximity to things that can affect the outcomes. By that metric, Rust is complex. Looking at a piece of code, it could do basically anything at all. It can get rewritten by a macro, operators can be overloaded, any line of code can hide implicit magic that you have no way of knowing about. Rust gets away with being complex by having lots of correctness checks. C++ is about as complex as Rust, but, without the correctness checks, it can be a nightmare. In Rust, it might be hard to tell what happens when you run some code, but at least you know it will be nothing terrible. what does simple mean? 1) Simple for me, writing the code 2) Simple for you, reading the code 3) Simple for the SREs who have to operate the code 4) Simple in the sense that there aren't many abstractions 5) Easy to refactor 6) Easy to use and test (a function is simpler than a bunch of objects) 7) Simple to debug (no state is simpler than yes state) Both Go and Rust score pretty high in aggregate on these different definitions of simple, but they get there in very different ways.
- neonsunset 3y agoGo has very weak compiler and completely falls apart on anything but trivial code. If you care about reaching performance ceiling while staying with higher level language - use C#. Good programmers know better than to confuse Go for a systems programming language.
- KingOfCoders 3y agoWould not use C#, been there, bought the T-shirt, too complicated. I love the simplicity of Go. "Good programmers know better than to confuse Go for a systems programming language." I'm only coding for 40y years now, there is still time for me to learn that wisdom I hope.
- stouset 3y agoGo was advertised as a systems programming language since day one. Then we went through a phase where Gophers started to redefine what a "systems programming language" is, so this could remain true. Now we're at the point where good programmers know that Go isn't one?
- pjmlp 3y agoThere is gccgo if you care for a good optimizer, unfortunely is stuck on Go 1.18 for ages.
- KingOfCoders 3y agoThanks, didn't know.
- kaba0 3y agoThat’s the same question as why JS is consistently much slower than Rust. They are not at all similar languages, there is absolutely zero reason to expect otherwise. Go is a managed language with a GC that will slow down threads at high allocation rates and have GC pause times. Also, the default compiler just spews out machine code with barely any optimization (hence the fast compile times). Note: this doesn’t mean tracing GCs are slow, they are in fact faster than RC. But Rust just simply has more control over what actually happens, than a managed language.