Hacker News new | ask | show | jobs
by avita1 1674 days ago
Cool article, I'm not sure I agree with the headline.

I used to write low-scale Java apps, and now I write memory intensive Go apps. I've often wondered what would happen if Go did have a JVM style GC.

It's relatively common in Go to resort to idioms that let you avoid hitting the GC. Some things that come to mind:

* all the tricks you can do with a slice that have two slice headers pointing to the same block of memory [1]

* object pooling, something so common in Go it's part of the standard library [2]

Both are technically possible in Java, but I've never seen them used commonly (though in fairness I've never written performance critical Java.) If Go had a more sophisticated GC, would these techniques be necessary?

Also Java is supposed to be getting value types soon (tm) [3]

[1] https://ueokande.github.io/go-slice-tricks/

[2] https://pkg.go.dev/sync#Pool

[3] https://openjdk.java.net/jeps/169

6 comments

Object pooling in Java used to be fairly common. I don't see it much anymore in new code, but used to run into it all the time when writing code for Java 1.4/5. Even Sun used pooling when they wrote EJBs. Individual EJBs can be recycled instead of released to the GC.

Nowadays the GC implementations are good enough that's it's not worth the effort and complexity.

Though now that I think about it Netty provides an object pooling mechanism.

Pooling objects (for the purposes of minimizing GC) is consider a bad practice in modern Java. The article suggests that compacting, generational collectors are a bad thing, but they can dramatically speed up the amount of time it takes to deallocate memory if most of your objects in a given region of memory are now dead. All you have to do is remove objects that are still alive, and you're done: that region is now available for use again. The result is that long-lived objects have a greater overhead.
Does object pooling still make sense for direct ByteBuffers nowadays?
Yes. Those aren't GC controlled so any arguments about GC is irrelevant with direct byte buffers.

Also, object pooling isn't really a GC related hack, it's more useful as a cache booster. Programmers like immutability and garbage collection but your CPU doesn't like these things at all. If you're constantly allocating new objects it doesn't matter if your GC kicks ass, because those objects will always be in parts of memory that are cold in the cache. If you allocate some up front and mutate them, they're more likely to be warm.

Obviously this isn't a language or even VM thing. It's a "mutable variables are good for performance" thing.

> Both are technically possible in Java, but I've never seen them used commonly (though in fairness I've never written performance critical Java.)

I don't know about the Java world, but in C#—especially in games written in Unity—object pooling is very common.

Writing High Performance .NET Code (https://www.writinghighperf.net/) has a chapter on this. In C#, time spent collecting depends on the number of still-living objects. That means you want objects you allocate to be short-lived (dead by the time GC happens) or to live forever (they go to the gen 2 heap and stay there). The book suggests object pooling when the lifetime of objects is between those two extremes, or when objects are big enough for the Large Object Heap.

But at the end of the section, the book says:

  I do not usually run to pooling as a default solution. As a general-purpose mechanism, it is clunky and error-prone. However, you may find that your application will benefit from pooling of just a few types.
What kind of things do you pool in Unity?
Unity aside, I believe it's useful in a lot of areas of gamedev.

Anecdotally, in a lower-level game engine I wrote at one point in C#, object pooling significantly reduced memory overhead (and IIRC increased framerates on complex scenes) when I scaled well past 1000 dynamic, moderately-lived entities. Particles, objects, projectiles, bad guys, etc. I believe can all benefit from pooling, assuming they aren't long-lived.

I do agree it can be error prone, but I'm convinced it's worth it for several places in gaming.

Ideally you would pool anything you might need to dynamically allocate during a level. You want to avoid allocations during game play entirely, if possible.

Unity itself will pool most media assets. Any given texture asset is shared between all object instances that use that texture. The programmer will end up pooling instances of their objects or just use structs and such. It can be tedious but I wouldn't call it more clunky than explicit memory management.

Large collections are actually not a problem at all in games as long as you only run the collection during a load screen.

I've seen it on a large site written in c#. Object pools of stream objects for serializing and deserializing data. This was 10 years ago.
Java has a pretty decent standard library with different list, map and set implementations and quite a few third party libraries with yet more data structures. Honestly, Go felt a bit primitive and verbose to me on that front on the few times I used it. Simplicity has a price and some limitations.

There are also other tricks you can do like for example using off heap memory (e.g. Lucene does this), using array buffers, or using native libraries. There obviously is a lot of very memory intensive, widely used software written for the JVM and no shortage of dealing with all sorts of memory related challenges. I'd even go as far as to argue that quite a few of those software packages might be a little out of the comfort zone for Go. Maybe if it were used more for such things, there would be increased demand for better GCs as well?

Object pooling is pretty common for things like connection pools. For example apache commons pool is used for doing connection pooling (database, http, redis, etc.) in Spring Boot and probably a lot more products. Also there are thread pools, worker pools and probably quite a few more that are pretty widely used and quite a few of those come with the Java standard library. Caching libraries are also pretty common and well supported popular web frameworks like Spring.

A typical Java based search or database software product (Elasticsearch, Kafka, Casandra, etc.) is likely to use all of the above. Likewise for things like Hadoop, Spark, Neo4j, etc.

Of course there's a difference between Java the language and the JVM, which is also targeted by quite a few other languages. For example, I've been using Kotlin for the last few years. There are functional languages like Scala and Clojure. And people even run scripting languages on jython, jruby, groovy, or javascript on it.

There even have been some attempts to make Go run on the JVM. Apparently performance, concurrency and memory management were big motivators for attempting that (you know, stuff the JVM does at scale): https://githubmemory.com/repo/golang-jvm/golang-jvm

Their pitch: "You can use go-jvm simply as a faster version of Golang, you can use it to run Golang on the JVM and access powerful JVM libraries such as highly tuned concurrency primitives, you can use it to embed Golang as a scripting language in your Java program, or many other possibilities."

Not sure if you're in on the joke, but for those who didn't go to the repo itself:

https://github.com/golang-jvm/golang-jvm

It's just a copy-paste of JRuby on April 1st and the readme now includes a rickroll.

Maybe it's irresponsible of them to leave it up in a way that Google still finds as a legitimate-looking search result.

LOL, I was not aware and stepped right into that.

There appear to be other attempts: for example https://github.com/zxh0/jvm.go (might be the same?)

Let's just say people have tried/joked about it but it never took off.

Its other way round. Implementing JVM in Go and not running Go over JVM.
> There even have been some attempts to make Go run on the JVM. Apparently performance, concurrency and memory management were big motivators for attempting that (you know, stuff the JVM does at scale):

This seems legit. Just links to their website/Wiki are not working right now.

Object pooling used to be more common in Java. Now it is mainly used for objects that are expensive (incurs latency) to create, not for GC reasons.
How have you found Go in contrast to Java. Is the simplicity worth it?
yes. golang is actually less restrictive than java. and avoids a ton of the bullshit abstractions you see in every java code base.
>ton of the bullshit abstractions

Not sure how that's the Java's problem. Most of these abstraction come older frameworks. You can have these same abstraction/design pattern in Go also.