Live data from Hacker News

Generics can make your Go code slower

planetscale.com

251–260 of 418 posts

Re: Generics can make your Go code slower

#251
post #133

Earlier quoted context omitted.

I think you're mistaken on nearly every count. :) First of all, Go and Java exist at roughly the same performance tier. It will be less work to make Java beat Go for some applications and vice versa for other applications. Moreover, typical Go programs use quite a lot less memory than typical Java programs (i.e., there's more than one kind of performance). Secondly, Go can make syscalls directly, so it absolutely can…

I disagree. Go is definitely not as fast as Java for throughput. It's gotten pretty good for latency sensitive workloads but it's simply left in the dust for straight throughput, especially if you are hammering the GC. Sure it can make syscalls directly but if you are going to talk about a maintainability nightmare I can't think of anything worse than trying to manipulate threads directly in Go. I had to do this in a…

> Build systems in Java by and large fall into only 3 camps, Maven, Gradle and a very small (but loud/dedicated) Bazel camp. Contrast that to Go which is almost always a huge pile of horrible Makefiles, CMake, Bazel or some other crazy homebrewed bash build system.

Well Go does not need a book of 400+ pages to understand Maven.

Re: Generics can make your Go code slower

#252
Really well written article. I liked that the author tried to keep a simple language around a fair amount of complex topics.

Although the article paints the Go solution for generics somewhat negative, it actually made me more positive to the Go solution.

I don't want generic code to be pushed everywhere in Go. I like Go to stay simple and it seems the choices the Go authors have made will discourage overuse of Generics. With interfaces you already avoid code duplication so why push generics? It is just a complication.

Now you can keep generics to the areas were Go didn't use to work so great.

Personally I quite like that Go is trying to find a niche somewhere between languages such as Python and C/C++. You get better performance than Python, but they are not seeking zero-overhead at any cost like C++ which dramatically increases complexity.

Given the huge amount of projects implemented with Java, C#, Python, Node etc there must be more than enough cases where Go has perfectly good performance. In the more extreme cases I suspect C++ and Rust are the better options.

Or if you do number crunching and more scientific stuff then Julia will actually outperform Go, despite being dynamically typed. Julia is a bit opposite of Go. Julia has generics (parameterized types) for performance rather than type safety.

In Julia you can create functions taking interface types and still get inlining and max performance. Just throwing it out there are many people seem to think that to achieve max performance you always need a complex statically typed language like C++/D/Rust. No you don't. There are also very high speed dynamic languages (well only Julia I guess at the moment. Possibly LuaJIT and Terra).

Re: Generics can make your Go code slower

#253

Earlier quoted context omitted.

It's impossible to spend any time tuning Go's GC parameters as they intentionally do not provide any. Go's GC is optimized for latency, it doesn't see the same kind of 1% peak latency issues you get in languages with a long tail of high latency pauses. Also consider API design - Java API (both in standard & third party libs) tend to be on the verbose side and build complex structures out of many nested objects. Most…

You tune Go’s GC by rewriting your code. It’s like turning a knob but slower and riskier.

You tune GC in Go by profiling allocations, CPU, and memory usage. Profiling shows you where the problems are, and Go has some surprisingly nice profiling tools built in.

Unlike turning a knob, which has wide reaching and unpredictable effects that may cause problems to just move around from one part of your application to another, you can address the actual problems with near-surgical precision in Go. You can even add tests to the code to ensure that you're meeting the expected number of allocations along a certain code path if you need to guarantee against regressions... but the GC is so rarely the problem in Go compared to Java, it's just not something to worry about 99% of the time.

If knobs had a "fix the problem" setting, they would already be set to that value. Instead, every value is a trade off, and since you have hundreds of knobs, you're playing an impossible optimization game with hundreds of parameters to try to find the set of parameter values that make your entire application perform the way you want it to. You might as well have a meta-tuner that just randomly turns the knobs to collect data on all the possible combinations of settings... and just hope that your next code change doesn't throw all that hard work out the window. Go gives you the tools to tune different parts of your code to behave in ways that are optimal for them.

It's worth pointing out that languages like Rust and C++ also require you to tune allocations and deallocations... this is not strictly a GC problem. In those languages, like in Go, you have to address the actual problems instead of spinning knobs and hoping the problem goes away.

The one time I have actually run up against Go's GC when writing code that was trying to push the absolute limits of what could be done on a fleet of rather resource constrained cloud instances, I wished I was writing Rust for this particular problem... I definitely wasn't wishing I could be spinning Java's GC knobs. But, I was still able to optimize things to work in Go the way I needed them to even in that case, even if the level of control isn't as granular as Rust would have provided.

Re: Generics can make your Go code slower

#255
post #232

Earlier quoted context omitted.

> but those numbers mean you'd want to avoid doing anything at all in a gamedev render thread that could potentially trigger a GC pause, because said GC pause will trigger a vsync miss. You might want to take a look at this: https://queue.acm.org/detail.cfm?id=2977741

I have, it's a decent read - although somewhat incoherent. E.g. they tout the benefits of GCing when idle, then trash the idea of controlling GC: > Sin two: explicit garbage-collection invocation. JavaScript does not have a Java-style System.gc() API, but some developers would like to have that. Their motivation is proactively to invoke garbage collection during a non-time-critical phase in order to avoid it later wh…

> So, no explicitly GCing when a game knows it's idle.

I mean, that is literally what the idle time scheduler in Chrome does. It has a system-wide view of idleness, which includes all phases of rendering and whatever else concurrent work is going on.

> Intertwined references from C++ -> Squirrel[1] -> C++ -> Squirrel meant the first GC would finalize some C++ objects, which would unroot some Squirrel objects, which would allow some more C++ objects fo be finalized - but only one layer at a time per GC pass.

This is a nasty problem and it happens a lot interfacing two heaps, one GC'd and one not. The solution isn't less GC, it's more. That's why Chrome has GC of C++ (Oilpan) and is working towards a unified heap (this may already be done). You put the blame on the wrong component here.

Re: Generics can make your Go code slower

#256

Earlier quoted context omitted.

The article is from a database company, so I'll assume that approximates the scope. My scope for the GC discussion would include other parts that could be considered similar software: cluster-control plane (Kubernetes), other databases, and possibly the first level of API services to implement a service like an internal users/profiles or auth endpoints. The tricky thing is GC works most of the time, but if you are wo…

> A good portion of production outages are likely related to cascading failures due to too long GC pauses, and a good portion of developer time is spent testing and tuning GC parameters. Can’t really accept that without some kind of quantitative evidence.

No worries. It is not meant to be quantitative. For a few years of my career that has been my experience. For this type of software, if I'm making the decision on what technology to use, it won't be any GC-based language. I'd rather not rely on promises that GC works great, or is very tunable.

One could argue that I could just tune my services from time to time. But I'd just reduce the surface area for problems by not relying upon it at all -- both a technical and a business decision.

Re: Generics can make your Go code slower

#258
post #125

Earlier quoted context omitted.

> Ignorance of how your language works is a bad thing. And I never said anything remotely close to contradict this statement. > Knowing where performance issues with certain techniques might arise is not premature optimization. It is: - Python: should I use a for loop, a list comprehension or the map function? - C++: should I use a std::list, std::vector, ...? - Go: should I use interface{} or generics? The differenc…

You're giving fine advice for well-scoped tasks with minimal design space (well, sort of - using std::list ever is laughable - but if you had said unordered_map vs. map, sure, so I take the broad point). But, some of us have been around the block a few times though, and now need to make sure those spaces are delineated for others in a way that won't force them into a performance corner. > until you have implemented y…

> using std::list ever is laughable

https://baptiste-wicht.com/posts/2012/11/cpp-benchmark-vecto...

> some of us have been around the block a few times though, and now need to make sure those spaces are delineated for others in a way that won't force them into a performance corner.

This, just like the rest of your comment, is just patronizing and condescendant.

> I don't mean to brag, but I guess I'm a lot better at planning ahead than you

See previous point...

> I also can't remember any time I had to reach for a hammer as big as reflect and didn't expect to very early on

This is not what I said at all. Let's say you know early on, before any code is written, you will need reflection. Can you tell me how many calls to the reflection API will happen before-hand? Is it `n`? `nlog(n)`? `n²`? Will you use reflection at every corner, or just on the boundaries of your task? Once implemented, could it be refactored in a simpler way? You don't know until you wrote the code.

> most of the time I know what I intend to do to my data

"what" is the spec, "how" is the code, and there is multiple answers to the "how", until you write them and benchmark them, you can't know for sure which one is the best, you can only have assumptions/hypothesis. Unless you're doing constantly exactly the same thing.

> but also maybe think about how many pointers you're going to have to chase and methods your users will need to implement in the first place because you probably don't get to "optimize" those later without breaking the API.

Basically, "write the spec before jumping into code". Which is the basis of "make it work, make it right, make it fast" because if you don't even know what problem you're solving, there is no way you can do anything relevant.

> You write about "the bottleneck", but there's not always a single bottleneck distinct from "the API".

I never implied there is a single bottleneck. But If you separate the implementation details from the High-Level API, they sure are distinct. For example, you can solve the N+1 problem in a GraphQL API without changing its schema.

If your implementation details leaks to your API, it just means it's poorly separated.

> You're never going to make an API like PyReader anywhere near as fast as GoReader, no matter how much optimization you do!

Because Python is interpreted and Go is compiled. Under the hood, the OS uses the `int read(int fd, void dest, size_t count)`, and there is an upper limit to the `count` parameter (specific to the OS/kernel).

Python's IO API knows this and allocates a buffer only once under the hood, it would be equivalent to having a PyReader implementation using a GoReader interface + preallocated []byte slice.

I can't tell you which one is faster without a benchmark because the difference is so subtle, so I won't.

Re: Generics can make your Go code slower

#259
post #203

Earlier quoted context omitted.

I don't want to try to bring up an exception that disproves your rule, but what about something like BEAM, where it has per-process (process = lightweight thread) heaps and GC.

I don't know anything about BEAM, but I don't think single-threading of any form really addresses the underlying problem. If you go to allocate something, and the system decides a GC is necessary in order to satisfy your allocation, then the GC has to run before your allocation returns.

You can't share objects across threads (called "processes") in BEAM, so it's very different. The GC only ever needs to pause one call stack at a time to do a full GC cycle. Memory shared across processes is generally manually managed, typically more akin to a database than an object heap.

Re: Generics can make your Go code slower

#260

Earlier quoted context omitted.

> Much easier than implementing the error interface in go. is this a joke? You have to import a third party package, just to implement an error interface? Here is Go example, no imports: type errorString string func (e errorString) Error() string { return string(e) }

It was not a joke. Let's look at a common example: you want to return two different types of errors and have the caller distinguish between them. Let me show it to you in rust and go. Rust: #[derive(Error, Debug)] pub enum MyErrors { #[error("NotFound: {0}") NotFound(String), #[error("Internal error")] Internal(#[source] anyhow::Error), } The equivalent go would be something like: type NotFoundErr struct { msg string…

I dont think you realize how ridiculous this comment is. Youre comparing 10 lines of Go, with 200 of Rust:

https://github.com/dtolnay/thiserror/blob/master/src/lib.rs

Post reply on HN