Live data from Hacker News

Generics can make your Go code slower

planetscale.com

151–160 of 418 posts

Re: Generics can make your Go code slower

#151
post #46

I'd argue that golang is inherently not a systems language, with its mandatory GC managed memory. I think it's a poor choice for anything performance or memory sensitive, especially a database. I know people would disagree (hence all the DBs written in golang these days, and Java before it), but I think C/C++/Rust/D are all superior for that kind of application. All of which is to say, I don't think it matters. Use t…

Rust itself will most likely get some form of support for local, "pluggable" garbage collection in the near future. It's needed for managing general graphs with possible cycles, which might come up even in "systems programming" scenarios where performance is a focus - especially as the need for auto-managing large, complex systems increases.

`Rc` and `Arc` have weak references which have worked just fine for me to break cycles in a graph. Not saying my use case is the most complex, but I haven't noticed this as a problem yet. YMMV

Re: Generics can make your Go code slower

#152
post #140
post #79

Earlier quoted context omitted.

Go is strictly less useful than Java because it has strictly less power. This is true for general purpose programming (though somewhat remediated through introduction of generics) it's doubly true for "systems" applications: No access to raw threads. No ability to allocate/utilize off-heap memory (without CGo and nonsense atleast). Low throughput compared to Java JIT (unsuitable for CPU intensive tasks). The only thi…

> On a stdlib level Java mostly wins This isn't even true compared to other comparable platforms like .NET, let alone Go which has hands down the most useful and well constructed standard library in existence (yes, even better than Python).

Yeah I don't buy that.

Especially not when things like this exist: https://pkg.go.dev/container

And things like this don't: https://docs.oracle.com/en/java/javase/17/docs/api/java.base...

As I mentioned Go does have a great HTTP and TLS stack but that doesn't do enough to put it on the same level.

Re: Generics can make your Go code slower

#153
post #128

Earlier quoted context omitted.

>I've seen this sentiment a lot, and I never see specifics. "GC is bad for systems language" is an unsupported, tribalist, firmly-held belief that is unsupported by hard data. I would argue it's not (very) hard data that we need in this case. My opinion is that the resource usage of infrastructure code should be as low as possible so that most resources are available to run applications. The economic viability of app…

> Systems/infrastructure code on the other hand is subject to very different economics. It runs all the time. The ratio of development time to runtime is incredibly small. We should optimise the heck out of infrastructure code to drive down resource usage whenever possible. I will assume by "infrastructure code" you mean things like kernels and network stacks. Unfortunately there are several intertwined issues here.…

>I will assume by "infrastructure code" you mean things like kernels and network stacks.

That, and things like database systems, libraries that are used in a lot of other software or language runtimes for higher level languages.

>The actual heap footprint of the Linux kernel is pretty small

And what would that footprint be if the kernel was written in Java or Go? What would the performance of all those device drivers be?

You can of course write memory efficient code in GC languages by manually managing a bunch of buffers. But I have seen and written quite a bit of that sort of code. It's horribly unsafe and horribly unproductive to write. It's far worse than any C++ code I have ever seen. It's the only choice left when you have boxed yourself into a corner with a language that is unsuitable for the task.

>First, we pay in completely different ways for writing this software in manually-managed languages. Security vulnerabilities. Bugs. Development time

This is not a bad argument, but I think there has always been a very wide range of safety features in non-GC languages. C was never the only language choice. We had the Pascal family of languages. We had ADA. We got "modern" C++, and now we have Rust.

If safety was ever good enough reason to use GC languages for systems/infrastructure, that time is now over.

Re: Generics can make your Go code slower

#154
post #92

Earlier quoted context omitted.

That's only 99% of the story. :) Having too many specializations of a C++ template can lead to code bloat, which can degrade cache locality, which can degrade performance.

You're definitely right. While it's not a particularly common problem, it does exist; one thing I'd really like to see enter the compiler world is an optimization step to use vtable dispatch (or something akin to Rust's enum_dispatch, since all concrete types should be knowable at compile time) in these cases. I expect it would require a fair amount of tuning to become useful, but could be based on something analogou…

enum dispatch in Rust is one of my favorite tricks. Most of the time you have a limited number of implementations, and enum dispatch is often more performant and even less limiting (than say trait objects)

Re: Generics can make your Go code slower

#155

Earlier quoted context omitted.

Well, that is simply not true at all. Go is a perfectly capable replacement for Java and C#. Many huge projects that would likely never be written in Python have been written in Go when they would have otherwise been written in Java or C# in years past: Kubernetes, Prometheus, HashiCorp Vault and Terraform, etcd, CoreDNS, TiDB, Loki, InfluxDB, NATS, Docker, Caddy, Gitea, Drone CI, Faktory, etc. The list goes on and o…

> What, exactly, are you saying that Go can't do that Java can? Runtime library addition (plugins) and dependency injection are two big ones. (We can argue the merit separately, but they're not possible in Go) I think if Java had easily distributable static binaries k8s would have stayed Java (it started out as Java).

> Runtime library addition (plugins)

https://pkg.go.dev/plugin

Linux only, but it exists and it works… I just wouldn’t recommend that particular pattern for almost anything.

Either some kind of RPC or compile-time plugins would be better for almost all cases.

- With RPC plugins (using whatever kind of RPC that you prefer), you get the benefit of process isolation in case that plugin crashes, the plugin can be written in any language, and other programs can easily reuse those plugins if they desire. The Language Server Protocol is a great example of an RPC plugin system, and it has had a huge impact throughout the developer world.

- With compile-time plugins, you get even better performance due to the ahead-of-time optimizations that are possible. Go programs compile so quickly that it’s not a big deal to compile variants with different plugins included... this is what Caddy did early on, and this plugin architecture still works out well for them last I checked.

> dependency injection

https://go.dev/blog/wire

Java-style DI isn’t very idiomatic for Go, and it’s just a pattern (the absence of which would not prevent applications from being developed, the purpose of this discussion)… but there are several options for doing DI in Go, including this one from Google.

Re: Generics can make your Go code slower

#156

> Inlining code is great. Monomorphization is a total win for systems programming languages: it is, essentially, the only form of polymorphism that has zero runtime overhead Blowing your icache can result in slowdowns. In many cases it's worth having smaller code even if it's a bit slower when microbenchmarked cache-hot, to avoid evicting other frequently used code from the cache in the real system.

The essay is missing a "usually", but it's true that monomorphisation is a gain in the vast majority of situations because of the data locality and optimisation opportunities offered by all the calls being static. Though obviously that assumes a pretty heavy optimisation pipeline (so languages like C++ or Rust benefit a lot more than a language with a lighter AOT optimisation pipeline like Java).

Much as with JITs (though probably with higher thresholds), issues occur for megamorphic callsites (when a generic function has a ton of instances), but that should be possible to dump for visibility, and there are common and pretty easy solutions for at least some cases e.g. trampolining through a small generic function (which will almost certainly be inlined) to one that's already monomorphic is pretty common when the generic bits are mostly a few conversions at the head of the function (this sort of trampolining is common in Rust, where "conversion" generics are often used for convenience purposes so e.g. a function will take an `T: AsRef` so the caller doesn't have to extract an `&str` themselves).

Re: Generics can make your Go code slower

#157
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…

> 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.

Java is better for GC throughput, but your claim was about compute throughput in general. Moreover, Go doesn't lean nearly as hard on GC as Java does in the first place (idiomatic value types, less boxing, etc), so GC throughput doesn't imply overall throughput.

> 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 previous Go project where thread pinning was important and even that sucked.

Thread pinning is a very rare requirement, typically you only need it when you're calling some poorly-written C library. If this is your requirement, then Go's solution will be less maintainable, but for everyone else the absence of the foot-gun is the more maintainable solution (i.e., as opposed to an ecosystem of intermingled OS threads and goroutines).

> That is just taste. Objectively collections and many other aspects of the Java stdlib completely destroy Go, I pointed out the good bits already.

Agreed that it's taste. Agreed that Java has more collections than Go, but I think it's a good thing that Go pushes people toward slices and hashmaps because those are the right tool for the job 90% of the time. I think there's some broader point here about how Java doesn't do a good job of encouraging people away from misfeatures (e.g., inheritance, raw threads, off-heap memory, etc).

> Again, taste. Java has a slightly steeper and longer learning curve but that is a cost you pay once and is amortized over all the code that engineer will contribute over their tenure.

Java has a significantly steeper/longer curve--it's not only the language that you must learn, but also the stdlib, runtime, tools, etc and these are typically considerably more complicated than Go. Moreover, it's a cost an engineer pays once, but it's a cost an organization pays over and over (either because they have to train people in Java or narrow their hiring pool).

> 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.

Go has one build system, `go build`. Some people will wrap those in Makefiles (typically very lightweight makefiles e.g., they just call `go build` with a few flags). A minuscule number of projects use Bazel--for all intents and purposes, Bazel is not part of the Go ecosystem. I haven't seen any "crazy homebrewed bash build system" either, I suspect this falls into the "for all intents and purposes not part of the Go ecosystem" category as well. I've been writing Go regularly since 2012.

> You don't escape CI because you used Go, if you think you did then you are probably doing Go wrong.

I claimed the CI burden is lighter for Go than Java, not that it goes away entirely.

> Java runtime trades simplicity for ability to be tuned, again taste. I personally prefer it.

I think it's difficult to accurately quantify, but I don't think it's a matter of taste. Specifically, I would wager that Go's defaults + knobs are less work than Java for something like 99% of applications.

> So no, I don't think I am mistaken. I think you just prefer Go over Java for subjective reasons. Which is completely OK but doesn't invalidate anything I said.

I agree that some questions are subjective, but I think on many objective questions you are mistaken (e.g., performance, build tool ecosystem, etc).

Re: Generics can make your Go code slower

#158

Earlier quoted context omitted.

I might have worded it differently, but yeah, of cause generics can make your code slower, what did people expect.

> I might have worded it differently, but yeah, of cause generics can make your code slower, what did people expect. ? In most languages, it is compile-time overhead, not runtime.

I wouldn't say "most", it's very variable. Also not unlike Go I think C# uses a hybrid model, where all reference types get the same instances of the generic function.

Re: Generics can make your Go code slower

#159
post #124
post #100

Earlier quoted context omitted.

Java can just be AOT compiled as Go, the only difference is that until recently it wasn't a free beer option to do so.

>until recently Actually, for a long time (almost 20 years, I think), the gcc subsystem gcj let you do AOT compilation of Java to ELF binaries. [1] I think they had to be dynamically linked, but only to a few shared objects (unless you pulled in a lot via native dependencies, but that's kind of "on you"). I don't recall any restrictions on how to use generated code, anymore than gcc-generated object code. So, I don't…

The interesting thing I always heard in the Java world is that AOT was actually a drawback as the virtual machine allowed for just in time optimizations according to hotspots. Actually if I remember correctly the word hotspot itself was even used as a tech trademark.

I was always a bit skeptical but given i was never much into Java i just assumed my skepticism was out of ignorance. Now with what I know about profile guided compilation I can see it happening; A JIT language should have a performance advantage, especially if the optimal code paths change dynamically according to workload. Not even profile guided compilation can easily handle that, unless I am ignorant of more than i thought.

Re: Generics can make your Go code slower

#160

Earlier quoted context omitted.

Rust itself will most likely get some form of support for local, "pluggable" garbage collection in the near future. It's needed for managing general graphs with possible cycles, which might come up even in "systems programming" scenarios where performance is a focus - especially as the need for auto-managing large, complex systems increases.

`Rc` and `Arc` have weak references which have worked just fine for me to break cycles in a graph. Not saying my use case is the most complex, but I haven't noticed this as a problem yet. YMMV

But the whole point of weak references is that they don't get shared ownership (i.e. extend the lifetime) of their referenced values. That's not doing GC. They're fine when there's always some other (strong) reference that will ensure whatever lifetime is needed, but that's not the general case that GC is intended for.
Post reply on HN