4 ms·
So in this case, with some modest performance engineering, Golang is surprisingly fast out of the box, and Rust requires more effort and increasingly less idiom
by MrBuddyCasino 3y ago
So in this case, with some modest performance engineering, Golang is surprisingly fast out of the box, and Rust requires more effort and increasingly less idiomatic code to reach the same result.
Is this typical?
- wokwokwok 3y agoWhile it’s a nice bed time story if you like go, the reality is no, it’s not typical. Go has a good perf story, but typically rust or c++ would be faster after heavy optimisation; and should be more or less on par with typical applications. This isn’t a critique of go, and shouldn’t surprise anyone. Typically go also has unexpected optimisation hoops to jump through and problems related to the heavy use of channels (see the well documented answer here: https://stackoverflow.com/questions/47312029/when-should-you-use-a-mutex-over-a-channel https://stackoverflow.com/questions/47312029/when-should-you...), so you would generally expect it to be slower… …but, naive implementations are always slower, and really, it’s probably much of a muchness out the box for most day to day uses. In almost all situations (even python or Java) you can get great performance if you invest time and effort in it. But idiomatic code typically faster than rust? No, not really.
- riku_iki 3y ago> but typically rust or c++ would be faster after heavy optimisation; it is concerning how much digging was required to optimize rust code in this case.
- fredrikholm 3y agoWhilst still being 1/3rd slower than the Go version.
- Filligree 3y agoThey could have used lifetimes. Instead they used elaborate workarounds, which turned out to be slower.
- pigpang 3y agoRust-protobuf is much slower than quick-protobuf or prost.
- v0y4g3r 3y agoThe Go version is not a naive implementation, it's also the well optimized version from VictoriaMetrics.
- sapiogram 3y agoThe Go code is already hyper-optimized by experts, just not by the blog authors, so you don't read about it here. As someone who has tried to write high-performance Go code on occasion, I can assure you that a ton of digging would have been required on that side as well.
- kjksf 3y agoTo bring this discussion back to earth, the Go code in question is: https://github.com/VictoriaMetrics/easyproto/blob/master/reader.go https://github.com/VictoriaMetrics/easyproto/blob/master/rea... I don't see why did you label Aliaksandr Valialkin, the author, an "expert". I mean, he's no dummy but what exactly makes him an expert on optimizing Go code? As someone who also writes Go, I don't see any "hyper optimizations" in the code. It just decodes the bytes of Protocol Buffer using straightforward code that I would expect a competent developer to write. It really is just: read bytes from memory ([]byte) and interpret them according to PB spec. There's only one trick there: unsafeBytesToString() that does no-allocation conversion of []byte to string. This is unsafe in general but safe in their specific case. And I've seen this trick before so it's not some secret, expert-only knowledge. Most comments here are like bad LLMs: hallucinating opinions without bothering to spent even few minutes acquiring the data to base those opinions on.
- sgift 3y ago> I don't see why did you label Aliaksandr Valialkin, the author, an "expert". I mean, he's no dummy but what exactly makes him an expert on optimizing Go code? https://victoriametrics.com/team/ https://victoriametrics.com/team/ - Let's see. Author of multiple performance-optimized libraries with a masters degree in computer software engineering and a background in highly scalable systems (as needed for adtech). Sounds pretty much like an expert for optimizing code to me. > And I've seen this trick before so it's not some secret, expert-only knowledge. So, if you know it it's not export knowledge or what's your argument here?
- kasey_junk 3y agoHe’s probably most well known in the golang community for fasthttp which is a widely used and highly optimized golang replacement for the golang stdlib. I’m a long term golang developer and I think calling him a golang optimization “expert” is fair. That said, I agree with your assessment about this particular code. It’s fairly straightforward idiomatic go.
- pjmlp 3y agoWhile I am not a big Go fan, most people validating Go's performance mix language and implementation, and forget gccgo also exists, sharing many optimizations offered by GCC's backend. Unfortunely, it seems stuck in Go 1.18, the last pre-generics version, with no roadmap for moving forward. Given Go's folks stance on generics, still a lot of Go code is compilable with gccgo.
- kosherhurricane 3y agoIt's really easy to improve the performance of Go implementation as compared to python or Java. There are lots of built-in tools to help you (like the profiler), and the resulting code is really very legible even to fresh college grads. This is based on my first hand experience, but YMMV.
- v0y4g3r 3y agoI think no. In this particular case it involves how Rust handles lifetimes compared to other languages with garbage collector. Essentially it's a comparison between reference counting and tracing garbage collection based on reacheability.
- MrBuddyCasino 3y agoYes, and this is what I was curious about, because I haven't seen the performance cost of this discussed a lot. Worded differently: does the strict concept of ownership/lifestimes in Rust bias a default (naive) implementation towards lower performance (eg due to required copying) when compared to a naive Golang (or even Java) implementation? I have no doubts that after heavy optimization, Rust beats languages such as Go & Java.
- pflanze 3y agoUsing clone or Arc with boxing everywhere to avoid using references with lifetimes at all will lead to code that's slower than Go/Java, yes, unless you're just cloning small objects that don't internally use heap allocations or your algorithm dictates to only need moves, not sharing, or perhaps except it will likely use less RAM which in some situations may still make it faster. But such "newbie" code will probably still be using some other existing code that is using references internally, which makes things faster for those parts. Also, the difficulty of the use of references varies depending on how long / indirect they are going to be used, in many places references are easy to deal with even for a beginner; so it becomes a question of how much the code relies on clone and reference counting. When I learned Rust, I actually never went with the "use clone or Arc to make your life easier while learning" recommendation but always used references and learned how to use lifetime declarations and program design to go as far with them as reasonable. TBF I had experience with C and C++ already. But once reasonably experienced working in Rust (after a year?), your code should be faster most of the time the way you write it on the first try without needing optimization work.
- pjmlp 3y agoWith the caveat that there are various kinds of "languages with garbage collector", including some where manual resource management is an option.
- sapiogram 3y agoI haven't dug into the code, but it's clear that the Golang library has had a ton of optimization work put into it by very knowledgeable people. Techniques like object pooling are highly error prone, and certainly not "out of the box" Golang. The Rust code and the blog post, on the other hand, seem to be written by someone less familiar with Rust and high-performance parsing. I think they would have avoided all their problems if they just used lifetimes to safely avoid copying from the start, instead of relying on increasingly elaborate workarounds like the `Bytes` crate. Apart from one "forces one to deal with the contagion of lifetimes" comment in the conclusion, they never mention why they didn't do this, even though it's clearly the idiomatic Rust solution. Maybe they had technical reasons for not doing lifetimes, but to me it just seems like unfamiliarity with Rust.
- formerly_proven 3y agoI’m unfamiliar with all of the libraries used here, however, the solutions from the blog post struck me as quite odd. serde::Deserializer solves a similar problem and makes it quite straightforward and safe to borrow from the input, if possible. Obviously, the input needs to outlive the borrowed values.
- littlestymaar 3y ago> Apart from one "forces one to deal with the contagion of lifetimes" comment in the conclusion I guess that must be the logical consequence of the “async function are contagious” meme… I wonder if at some point we end up with people arguing that dynamic typing is obviously better because it avoids “contagion with types”.
- lossolo 3y ago> Techniques like object pooling are highly error prone, and certainly not "out of the box" Golang. This one is actually out of the box in Golang, it's called sync.Pool and is accessible in the standard library. It's very easy to use and not error prone, I've used it many times without any issues. But creator of VictoriaMetrics is indeed someone very knowledgeable and known in Golang community in context of optimization.
- pigpang 3y agoIdiomatic quick-protobuf is much faster than non-idiomatic rust-protobuf: https://github.com/tafia/quick-protobuf/tree/master/perftest https://github.com/tafia/quick-protobuf/tree/master/perftest
- kosherhurricane 3y agoMore or less. For a language designed for fresh-out-of-college engineer to pick up in a few weeks and be effective, it is very easy to squeeze out a lot of performance. * built-in profiler. * built-in escape analysis tool. * It's easy to pass pointers instead of copying data. * []byte is sub-slice-able, with a backing array. This does throw people off occasionally, but the trade off is performance. * Go lets you have real arrays of structs, optimizing cpu caches. * Built-in memory pools And more. And if you look at "non-idiomatic" performance code, they are surprisingly legible by the said fresh engineer. It's as if the designers didn't want to give up all the usual C performance tricks while making a Java/Python kind of friendly language, and this shows. Of course Go can go only so far, due to the built-in runtime and GC. But it gets very far. Much farther than at first glance, or second glances that language snobs would give credit for.