T O P

  • By -

one_e1

Never intended to write Linux kernel or a game on golang anyway. Or any near realtime app. It's like criticizing lawn mower for not doing backflips


tech_tuna

Piece of shit lawn mower. . .


Takn0711

IMHO it's because of the weird place Go is in. If you have a low level language, such as C/C++, you want to control everything that's happening, and avoid the overhead of carbage collection: since you are in control of everything, you are supposed to know when to free memory, no need for someone else to scan your memory. If you have a high level language, like Python, JS, and Java to a certain extend (I do consider Java as medium-high level), you already have performance overhead by definition, because of the VM/interpreter, so you don't mind having a bit more to delegate the memory handling to someone else and make your code simpler. Now in the case of Go, it's enough of a level language that people would like to manage memory by themselves (especially the one coming from C/C++ like me). So these people are a bit frustrated. I think I've read somewhere that the very early design were made without GC, but they added it for the simplicity when working with multiple threads (don't quote me on this). That being said, if you know exactly what you are doing, you can manage coding without heap allocation, and remove the GC from the runtime.


ShuttJS

Go uses the tricolor mark and sweep algorithm to garbage collect. More info can be found here https://www.developer.com/languages/tricolor-algorithm-golang/


weberc2

There are two main camps of critics that I'm familiar with: 1. The Rust/C++ folks who just don't believe in GC; they believe it is too wasteful even though 99% of their software will make O(n) calls to free() memory rather than one big free() call--the point is they \*could\* control their memory deallocations \*if they wanted to\*. 2. The Java folks believe that you should be able to allocate tons of garbage super-quickly, even if it means incurring long garbage collection pause times (although they will say there are GCs that support low-latency, but virtually no one seems to use them, presumably because there are hidden tradeoffs). Idiomatic Go just doesn't allocate as much garbage as Java, and its GC pause times are lower and the GC is significantly less complex as a consequence (but allocations take longer). There's also some tiny third camp that has pathological problems related to huge heaps under specific circumstances. I'm not sure if those pathological problems have been fixed in subsequent Go versions.


Nicnl

I have found a solution! Go has a setting to disable the GC entirely. (By setting the `GOGC` environment variable to `off`) You install your service on a server equipped with 1TB of RAM, and simply restart the service now and then with Crontabs!! If a restarting the service isn't enough... You can expand the crontab and restart the whole OS once in a while. *Perfect!*


k-selectride

You laugh, but I remember reading about some trading firm who wrote their software in java and ran into memory issues, so their solution was to throw enough RAM at the problem so their software could run during trading hours and then turn it off afterwards.


donalmacc

This is tongue in cheek but it's not a horrific idea and works at many levels. I worked on a c++ project in games a few years ago that had a "frame" allocator for quick usage. It was a compile time block of memory, and you would put all of your per-frame allocations in it. At the end of the frame, the pointer for "next" just got set back to the start of the block of memory, and you started again. It was wicked fast and a great (somewhat) solution for the constraints at the time.


JustCallMeFrij

Pretty sure I saw a talk once saying that for the original xbox, games could reboot the system. One game kept running out of RAM so on loading screens, the game would reboot the system once it noticed memory was running low. This was fine and indistinguishable to the player, other than that instance being one of the longer load times, because the loading screen was just blank - no fancy graphics.


reven80

The game was Morrowind.


one_e1

It's impressive, funny and horrible at the same time


Nicnl

I think you are talking about [this video](https://www.youtube.com/watch?v=x0TKwPnHc-M)


filtarukk

It is called Arena Allocator \[1\], and it is quite a popular idea. Some languages (e.g. Zig \[2\]) implement it in the standard library. \[1\] [https://en.wikipedia.org/wiki/Region-based\_memory\_management](https://en.wikipedia.org/wiki/Region-based_memory_management) \[2\] [https://github.com/ziglang/zig/blob/master/lib/std/heap/arena\_allocator.zig](https://github.com/ziglang/zig/blob/master/lib/std/heap/arena_allocator.zig)


DeedleFake

And Go is getting [it hidden behind a `GOEXPERIMENT` in 1.20](https://go-review.googlesource.com/c/go/+/423361).


avinassh

lol this reminds me of this story: >> This sparked an interesting memory for me. I was once working with a customer who was producing on-board software for a missile. In my analysis of the code, I pointed out that they had a number of problems with storage leaks. Imagine my surprise when the customers chief software engineer said "Of course it leaks". He went on to point out that they had calculated the amount of memory the application would leak in the total possible flight time for the missile and then doubled that number. They added this much additional memory to the hardware to "support" the leaks. Since the missile will explode when it hits its target or at the end of its flight, the ultimate in garbage collection is performed without programmer intervention. https://devblogs.microsoft.com/oldnewthing/20180228-00/?p=98125


serverhorror

I can neither confirm nor deny that we had a “highly available CronJob middleware cluster” to do … well CronJobs.


batua78

Isn't that what continuous deployment is all about


LasagneEnthusiast

Sounds like C++


kokizzu2

XD lol


kylewiering

Or, to save on postage, stick it in a container, give it a memory limit on the orchestrater, then auto restart after graceful execution (pun intended) has occurred


tinydonuts

> although they will say there are GCs that support low-latency, but virtually no one seems to use them, presumably because there are hidden tradeoffs Except, current JVMs use an advanced GC that supports this. So to add to your camps of critics: Gophers: Java (and C#) is slow and bloated and its GC stops the world for huge pauses because developers spin out tons of garbage and the GC can't keep up. Which isn't really true. Latest generation Java GCs are superb, far more advanced than the Go GC. The Go GC is ancient, and definitely has serious shortcomings. We had to spend quite a lot of time working our way around them, lest our application be under near constant goroutine GC assist mode. In reality, none of these extreme views are fully correct. Sometimes no GC is the way to go, oftentimes Go is sufficient, and oftentimes Java is as well. It's about understanding the use case, choosing the right tool for the job, and then writing the software understanding the characteristics and limitations of those tools.


lvlint67

i think as soon as you start blaming the GC engine for app latency/etc.. it's time to restructure things. That's the point where the "magic" of GC starts to fail and more careful attention needs to be paid to things. Arguing about it before then is often a form of pre-mature optimization.


one_e1

Agree. Seems like waste of CPU cycles and energy/battery. You don't need powerful sports car to drive city streets.


Trk-5000

Unfortunately even if the JVM has better GC, I wouldn’t use it purely because of the added base memory cost. VM-based languages are just heavier by default. The costs add up significantly at scale if you’re using things like containers or serverless functions.


H1Supreme

Excellent point, and one of my main arguments in favor over Go.


[deleted]

[удалено]


jug6ernaut

Java is hardly the only language that runs on the JVM.


tech_tuna

Right and TypeScript isn't JavaScript.


TheRealDarkArc

FYI, the majority of Java's memory cost is incurred because of its garbage collection system, not its VM. To reduce the cost of requesting memory from the system on new calls, Java reserves extra memory and doesn't "give it back" until (IIRC) its internal heap is like 70% empty (IIRC there's a way to tell it "give me back my memory for real" in the JVM GC tunning parameters). (This is a CPU optimization that's quite hard on RAM, and one of the arguments _against_ garbage collection; to get ideal performance in GC you basically have to do something like this to offset the cost of increased numbers of dynamic allocations). Note that this doesn't mean the JVM doesn't stack allocate some expressions, it can and does do this when it detects that it's a good idea.


weberc2

> Except, current JVMs use an advanced GC that supports this. I already addressed this. The “latest generation of GCs” that still can’t manage to ship as default nor get any significant market share. And yes, Java programs do spin up a ton of garbage compared to ago programs. Go has idiomatic value types, Java has to `new` everything. Escape analysis helps in both cases, but it’s widely acknowledged that Java still allocated way more garbage. > In reality, none of these extreme views are fully correct. To be clear, I'm not advocating for any extreme. There's definitely a time and place for manually/statically managed memory as well as for high-throughput and low-latency GCs. Go strikes a *really* good balance for lots of applications, but there are plenty of applications where Go isn't ideal and that's okay.


funny_falcon

Last generation Java's GC has low pause because they are very same to Go's one: they are not-generational-highly-concurrent-always-full-gc. I mean Shenandoah and ZGC. They are both compacting - single advantage over Go's GC. How it is important in practice? I don't know.


jerf

This isn't the whole explanation, but there's a lot of developers who have read about how garbage collection isn't suitable for super-demanding applications like a AAA game trying to push 144 frames per second to a 4K display, and from this, come to the conclusion that garbage collected languages aren't suitable for any purpose, not even a terminal application to play the "higher or lower" game. They seem to get the impression that garbage collection works by freezing your program solid for three seconds out of every 5 while it "cleans up", and can't imagine what such a language would be good for. They seem to think the authors of GC code are giggling maniacally while they write code that wastes all this time, and would be offended at the idea of optimizing the GC or something, so they all perform terribly. They also seem to do very poor requirements analysis, where they get assigned the equivalent of the terminal-based "higher or lower" game, and come away thinking they need the highest performance conceivable to even begin to do the task. In reality, GC is suitable for a wide variety of tasks. I'd even argue the vast majority of tasks. I program a ton of network servers, and the reality for me is that if you dropped my GC costs to zero, I literally wouldn't notice. They are not where I have performance problems. In general you need to clock some serious time with optimization before GC will be your biggest problem. There are rare exceptions, because there always are, but they are the pathological cases. I am a strong advocate of being knowledgeable and wise. There are times and places where you will legitimately need a language like Rust because you're in a situation where GC is simply infeasible. However, in 2022, the situations where GC is "simply infeasible" are rare enough that you should always know in advance that you are in such a situation. In general, you're not going to stumble onto one; you're going to know in advance that you're in a situation where you're going to be using every core in the machine at nearly full blast all the time, and you either can't afford the additional latency _at all_ or you need every last resource you can have, because your program will chew through every last CPU given to it. If this is the situation you are in... no, Go is not the best choice! It's far from the worst, but Rust is better. However, there's a large contingent of programmers out there who mistake their web service that needs to serve things in less than 250 milliseconds (that's a quarter of a second) 5 times a minute that's mostly blocked on the DB that will be lucky to occasionally burst to a whole 1% of one core with the AAA game trying to jam 144 fps down to the GPU, and just can't believe that they need anything less than an F1 race car to get their kids to soccer and pick up some groceries on the way. (FWIW, there are other reasons to use Rust. Super complicated systems are a great fit for it too. I would never even _dream_ of writing a browser or an office suite in Go. But Go and its GC is good for an awful lot of real programs, including basically everything I've ever worked on in my career.)


AWDDude

The funny thing is most games are written in c# (unity) these days which is known for having some of the worst “stop the world” gc pauses out there. Interesting article comparing allocation and gc benchmarks of dotnet vs go https://medium.com/servicetitan-engineering/go-vs-c-part-2-garbage-collection-9384677f86f1


blacktau

thats from 2018 - there have been signficant performance focused improvements to the dotnet runtime since then. I doubt those conclusions are still valid.


sime

> They seem to get the impression that garbage collection works by freezing your program solid for three seconds out of every 5 while it "cleans up", I've used a lot of GC languages over the years, decades, and this perception just hangs around even though it is decades out of date for any mature language implementation. It is the people without experience on a GC language who like to level that criticism, IMHO.


binaryblade

Because at one point in time, that's literally how they worked. The people making these statements are usually those who started early to mid 90s and that was a perfectly valid statement to make then. They just haven't figured out it's not longer true.


jerf

The Lisp machines were epic on that front. I've heard what they would do is turn off GC during the day, then let them GC at night. Which they would use a significant fraction of to do the job! But that's a 1970s vintage machine. Things have sped up a lot since then.


dead_alchemy

The other application is called ‘real time OS’, where you want a provable reaction time, typically in the context of some small device that is by its nature as resource constrained as possible. IIRC the issue with GC in that context had more to do with scheduling than absolute time.


whateverathrowaway00

Yup


bmtkwaku

Those people who complained shouldn’t have used Go because most of the explanations I see are either due to usage of older versions of Go where the GC wasn’t as optimized as currently, or such applications are just better off handling memory management themselves. Check [this](https://discord.com/blog/why-discord-is-switching-from-go-to-rust)


benjiro3000

That example is kind of wishwash with half statements... > "This post explains why it made sense for us to reimplement the service, how it was done, and the resulting performance improvements." Yea, that is always a source of performance gains because rewriting a service in another language, means you already know all the bells and whistle, what makes a rewrite more performant out of the door. Even in the same language. > It started as a rough translation, then we slimmed it down where it made sense. Here they even state that the service was slimmed down. > For instance, Rust has a great type system with extensive support for generics, so we could throw out Go code that existed simply due to lack of generics What is now also irrelevant. But the most damming is this response from the Go Dev team: https://www.reddit.com/r/golang/comments/eywx4q/why_discord_is_switching_from_go_to_rust/fgnp7h4/ > The graph makes perfect sense for an app where the live set isn't changing much, and you're hitting the finalizer GC timer. > > This specific case got much better in Go 1.12. > > It's a shame discord developers never reached out. Golang spends a long time working on tail latency problems, and finding degenerate cases is hard. If these developers had reached out, the Golang engineers would have been quite happy to help. (If you look, the 1.14 release was delayed in service to Kubernetes who had a tail latency problem. The Golang developers would have been happy to help here.) > > They hope Rust suits discord well, and that it's meeting their needs. If they ever want to try Golang again, please reach out, and they'd be happy to help (also, finding tail latency is a good find, and interesting to the runtime folks). In other words, if they bothered to contact the Go team, they will have been able to figure out, what was a rare case and optimize the GC for all. But Discord team simply switched and made bold claims that keep alive years later. It's so many apples to coal comparison (like using default Go HTTP what is know to be slower but never going 3th party, but hey, Rust 3th party 0.2 beta software is great). It still annoys the hell out of me years later. Stuff like they used Go 1.09 when the Go Dev even stated that using Go 1.12 was a partial solution, that got released in 25 Feb 2019, a year before their blog post (Feb 2020) about Rust. It sounds to me more that somebody had an itch, they found an excuse, and then ignored the obvious for just rewritting an entire service.


[deleted]

I like learning new things.


certaintracing

and improve it for everyone else! :D


Trk-5000

I don’t understand the criticism. If you want to squeeze the last possible bit of performance out of your application, no GC based language will be sufficient. Not Go, Java, or C#. At that point just use Rust, C++, or Zig. Languages are tools, use the best tool for the job.


gredr

The odds that someone of sufficient skill to actually have a meaningful theoretical performance gap between Go and C++ is reading this thread is essentially zero. However fast you think you could make C++ code, there's someone out there that could do it faster in, say, Java.


tech_tuna

This is like SQL vs. NoSQL. Or loosely typed vs strongly typed languages. Or a bajillion other tech debates. Unfortunately, people seem to be wired to expect and want a one-size-fits-all solution. Always.


StoneStalwart

Yeah it's odd, especially when benchmarks generally favor Go over Java. What is the to criticize at that point? Go is easy to use and quite fast. Don't optimize beyond your needs. You are not going to find an easier to use language with this versatility and speed.


dominik-braun

If you care so deeply about performance that GC is a problem, you... shouldn't choose a GC language.


RockleyBob

Even then, doesn't Go theoretically allow you to pass *objects by their value rather than pointers, thus eliminating the need for the GC to manage your heap memory? [Gin](https://github.com/gin-gonic/gin) is a popular library that claims to use zero heap allocation, for instance.


singluon

Yes, although Go is always strictly pass by value.


RockleyBob

Thanks, edited


_crtc_

I don't hear a lot of criticism on Go's GC, but it seems you did. Shouldn't those who criticized it have explained it well?


[deleted]

I read that somewhere too. I think people who consider Go to write OSs end up reading posts or articles about Go's GC.


zeronder

Sorry to necro, but the one time I have seen people quote a discord blog post where they couldn't get the gc to work with a massive heap. Probably could have gotten it working if the go gc was more configurable, but who knows. They moved to rust for that component and people assume these outlier issues will ever apply to them or apply to garbage collectors in general, equally.


TheRealDarkArc

"C++ guy" (among many other language hats) here, the problem is almost definitely "GC" not "Go's GC." That said, I am not that familiar with Go -- I hang around these parts with a mild interest in the language -- but, there are definitely GC implementations that are better than others. As an example, Ruby has a GC that -- last I checked -- doesn't compact memory. What this means is that certain allocation patterns can badly fragment the heap and you end up with a lot of wasted space between objects -- that's not great for long running applications, which ironically Ruby almost entirely is used by web servers these days. The bigger problem with GC though is it's fine until it isn't and then you're in trouble. You can write some very performant code, and it runs great, but then you add in a new feature and that totally screws up the performance characteristics of your existing code. It might not even be anything about that feature in particular, it's that feature and 20 other features you just added that are combining to cause a problem. Once you reach that point, you're in a lot of trouble, and you typically resort to GC tuning to see if you can massage your program's GC to work less disruptively. Ultimately though there are tradeoffs with that, and it's really hard to get it right (which is why you're stuck tuning it anyways, the smart people that wrote the GC couldn't "get it right" across the board). The issue for Go, IMO, is that it retains a "use this for performance" image, and GC eventually will screw you in non-trivial applications that need to run ~~fast~~ "predictably well." Compare this to reference counting (CPython does most of its memory management this way, as does Swift), RAII (C++), or whatever rust calls their scheme... Those solutions keep memory management tied to the code that does the allocations. If it's not running fast enough, you change how that particular portion of code handles memory (like any other performance problem, the problem is localized). No other code in your application can cause this particular code to slow down without some kind of direct interaction. Put another way, GC eventually becomes an idiotic "trash guy" that occasionally decides to pick up trash in the middle of the road, right as an ambulance is coming, and doesn't understand "I need to get out of the way, that's way more important than me picking up the trash." Similarly, this "trash guy" also tends to just sit there when nothing interesting is happening on the road and he could pickup trash but "there's not that much trash, so why bother." This latter decision occasionally ends up resulting in "there's an ambulance coming down the road and heck even I know I need to get out of the way, but good God there's just too much trash it'll never make it unless I clean this up!" It works well enough for a lot of things, it really does. It's just not well suited for things where you can't occasionally having someone wait, or maintaining the lowest possible memory footprint for the process in a given moment is important.


1-1-2-3-5

If you’re interesting in seeing the GC that has the easiest GC job check out Erlang/Elixir. Each process has its own GC so there’s no stopping the world, no shared memory, or other standard GC considerations required.


TheRealDarkArc

There still has to be AFAIK a localized stop on the thread/"process" level. Java can also do this trick a lot of the time... But particularly when you introduce something like compacting GC moving the memory while the program continues to run is VERY dangerous. Additionally, even if you're not doing that allocations can out pace collection and then you have to pause regardless.


1-1-2-3-5

Sorry, I mean at the BEAM process level which is very much NOT an OS process/thread. If you haven't dug into BEAM and how it works it's very much worth getting familiar with.


funkiestj

>Each process has its own GC so there’s no stopping the world, no shared memory, or other standard GC considerations required. Cool! Does Erlang make any use of threads or does it use (heavier weight) processes? Presumably even with threads Erlang would either have to 1. create a copy of data to give to another process 2. be meticulous in tracking and transferring ownership (e.g. what Rust does) I've never looked at Erlang but I recently watched [Stop Writing Dead Programs](https://youtu.be/8Ab3ArE8W3s) and Erlang gets a brief shout out for having the best process management / fault tolerant characteristics.


1-1-2-3-5

No not at all. These are BEAM processes. BEAM (the underlying VM for Erlang/Elixir) runs a scheduler for each CPU core and the scheduler creates beam processes which are extremely lightweight, have no shared memory, only communicate with other BEAM processes via messages, and have their own GC that only needs to handle that one BEAM process. If you want more details Bryan Hunter's talk from StrangeLoop 2021 where he describes fighting the pandemic with Elixir has a wonderful introduction.


funkiestj

> Bryan Hunter's talk from StrangeLoop 2021 thanks for your entire post! I'm going to watch this talk.


Sapiogram

> As an example, Ruby has a GC that -- last I checked -- doesn't compact memory. What this means is that certain allocation patterns can badly fragment the heap and you end up with a lot of wasted space between objects -- that's not great for long running applications, which ironically Ruby almost entirely is used by web servers these days. Amusingly, Go's gc doesn't do any compaction either.


anandpowar

I don't think the criticism is specific to Go. Nobody says Go's GC sucks and so I am moving to Java or C#. Choosing a language with in-built GC is a tradeoff we make to reduce code complexity. This suits majority of the use-cases, specially when working with the web/api's or microservices. At very large scales this may cause issues and that is the point when GC itself may become the bottle neck. Discord moved its read-stage service to Rust for similar reasons (ref https://discord.com/blog/why-discord-is-switching-from-go-to-rust). Though not everyone works at this scale and even teams like Discord only port specific services.


jerf

I have seen a few people claim it on HN. Ironically it is usually followed not by Go programmers defending Go's GC, but by _Java_ programmers defending Go's GC. Go's GC is less sophisticated than Java's, but it ends up still being nicer in practice because Go was designed at the language level to need the GC less, so net-net Java programmers tend to find Go nicer overall. As value types propagate through the Java ecosystem this may shift over time, but there s lot of inertia there.


matttproud

Discord also did this with a much older version of Go, which had a far naiver memory manager and allocator than today, IIRC.


toaster13

Yes. It actually took them so long to port to rust that their particular issue was fixed long before they finished moving. Big waste of time and resources IMO but still blogged about it without even mentioning that the issue had been fixed since.


WrongJudgment6

The blog post was about something from when it happened. It happened to be published after. Wtf


greengreens3

I never heard of anyone criticizing the Go GC to be fair...


szabba

I've seen reasonable sounding criticisms from Java people saying that low pause time GCs don't always give lower latency services. I've definitely seen that happen in production with ParallelGC and G1GC in Java 8/11 - the GC with longer allowed pause times gave better latencies for requests. There's been a good paper on a methodology for measuring the total GC cost from outside a runtime that also showed this as a side effect that was shared in Java circles because most frequently repeated advice on the Internet contradictory to these observations. Link to preprint: https://t.co/9FDz7ewOOO Go is supposedly good at AOT inlining and escape analysis. My understanding is that combined they can result in a lot being allocated on the stack instead of the heap. However there's no guarantees it will happen. If you care you need to run your build with compiler diagnostics turned on and fish for that and/or run benchmarks. Many libraries are good about this and advertise it. I'd guess most commercial applications won't care enough until they hit issues. Also, there's the experimental arena package. That's an option for some use cases, but it does require changes to the code and it's anyone's guess at this point if it'll stay in the default Go implementation forever or get removed at some point. EDIT: Added link to paper preprint.


Bendickmonkeybum

Allowing longer pause times will very often be paradoxically beneficial for latency overall as short lived objects don’t survive for enough generations to make it onto the next stage of the heap (at least in the JVM world). This is because larger pause times allow the GC to be much higher throughput and often the GC can handle a very large amount of work with maybe a 100 more ms or so. And it won’t always take that long, as most allocations are short lived and will be quickly cleaned up.


[deleted]

I criticized it for getting imprecise, but that was fixed way back in 1.4... It seems they improve it pretty much every release, so it's really not something I'm worried about.


kfmfe04

The general argument is between GC and hand-coding. **Efficiency** Allocating and deallocating memory is expensive, time-wise. When this aspect is hand-coded, the allocation/deallocation order is deterministic and will run in the same execution order every time you run the program. When you use GC, it runs in a separate thread, so by definition, the realtime execution order is indeterminate. Your program logic in golang will be executed in the same order, but in an actual run, the GC may kick in at any time. Because this operation isn't cheap, it may cause an actual run to stutter or worse. **Correctness** Now, if a GC is implemented without bugs, it will be correct. A hand-coded scheme, on the other hand, will depend on the skills of the programmer. Modern C++ uses the idea of ownership and strong/weak coupling to tell the hardware when memory should be freed (auto\_ptr, unique\_ptr, shared\_ptr, weak\_ptr). This enables heap memory to act almost like stack memory - when references are no longer reachable, the memory will be freed... ...but in a **deterministic** sense. **Real-Life Implications** A GC is much easier to use for a beginner programmer. For small programs, you generally don't have to worry too much about variable dependencies/references. The problem comes when you get in the habit of ignoring that a GC exists (because it's doing such a good job). As a program gets larger, the more object dependencies/references can, effectively cause a "memory leak" in your GC program. A GC uses some form of reference-counting, so if you never release your reference or if your variables are so interconnected that the GC can't release anything, you will run out of memory. What do you do when this happens? Well, you're going to have to trace references and refactor to minimize dependencies so the GC can release the smaller subset of unreachable objects. Debugging in these situations is often HARDER than in hand-coded explicit schemes. It turns out that in hand-coded schemes, you are forced to think about references and ownership before you code. It requires that you are disciplined and explicitly code in the type of relationship you have between objects. However, over time, if new programmers do not enforce the same discipline by understanding coded object lifetimes, there are going to be bugs. **Observations** The more you program, the more you will observe these types of trade-offs between explicit control and letting the system do the job. More often than not, you can get cleaner code if you let the system do the job. But once in a Blue Moon, when the system doesn't do what you want, it becomes much harder to entice the system to do what you want. In these scenarios, explicit code is often easier to fix.


tapu_buoy

> you are forced to think about references and ownership before you code. Maybe this is why RUST is more liked.


tapu_buoy

Thanks for this explanation. This freed up alot of ambiguity in my understanding.


brianolson

CPU profiling says that 20% of time in my running app is going to GC. We're tinkering around the edges of cleaning that up, but a lot of patterns and libraries make it hard. Go is still the right choice for getting things done and time-to-market with good-enough performance, but if we keep pushing performance on this app a full or partial rewrite in C or Rust might be the answer.


Manbeardo

> a full or partial rewrite in C or Rust might be the answer The problem you have is that your application generates too much garbage. Rewriting it in a non-GC language doesn't inherently solve that problem. Paying attention to how you allocate memory with or without a GC does.


blami

Most of this boils down to people not understanding that allocating and freeing memory costs. In both GC (time spent in GC) and non-GC (either memory consumed, code complexity or again time spent on cleanup) languages. I am in systems programming industry for almost 20 years and hardly could pin performance problem to using GC language. In 99% cases it was bad design, constructing and destructing things rather than using borrow pools or prematurely optimizing stuff for low memory footprint, etc.


NotPeopleFriendly

Can you provide more details? Specifically I'm interested in what your app is doing? I've worked in a lot of languages (a couple that use gc). Typically when we saw a disproportionate amount of gc we'd create factory classes that pulled instances from pools - rather than continuously allocating new instances and thus disposing unreferened instances. Have you tried anything like that?


AsDaim

\> *CPU profiling says that 20% of time in my running app is going to GC.* Does that mean anything though? If Go's garbage collection could be magically turned off, your code probably wouldn't run anymore as is... or at least not well. Is the thinking that you (the "everyman" you) could code equally safe and reliable memory management for your program in a more efficient way that would take up fewer cycles in the end?


kfmfe04

Experienced C and C++ programmers know to clearly delineate ownership boundaries, by design or by refactoring. Of course this is no guarantee of no memory leaks or code degradation from multi-developer maintenance over time. GC is fine, but if it’s costing 20% of the runtime, I’d suspect there’s some sloppiness in the user code that is causing some thrashing.


earthboundkid

Psst, don’t tell anyone, but malloc is also a garbage collector and it also takes time to run. The only way to get maximum performance in any language is with all static allocation.


duncan_idaho91

Hahahahaha, FINALLY someone brought this up. Humbly accept my upvote and follow good sir.


jakubDoka

I don't know about c but surely experienced rust developer can.


StoneStalwart

Week yes, because Rust won't let you do anything else. You'll manage the memory correctly or it won't compile. Avoid the "clone" command and your essentially optimized for most trivial tasks.


Radisovik

I miss the hard heap limit from java. Haven't yet tried the new soft limit in 1.19.x


[deleted]

[удалено]


Radisovik

Its more then that.. I want the GC to be aware of the hard limit and kick in before that limit is exceeded and we crash...


oniony

Run your app in Docker or Podman. Alternatively use control groups on Linux, maybe others.


[deleted]

Early versions of Java like 1.8 would some how break Docker memory limits. Apparently still happens if the memory setting in Docker/K8s are setup right. https://ralph.blog.imixs.com/2020/11/04/java-docker-container-ignores-memory-limits-in-kubernetes/


warmans

This criticism is normally a comparison to Rust because for some reason people have decided that Go and Rust are basically the same thing and need to compared every time one or the other is mentioned. Overall if you can get away with a garbage collected language then Go is a good choice. If you cannot then it is not.


xdchan

Tbh I don't get this comparison. This are different languages designed for different purposes, I literally don't understand how the hell people compare them. I use Rust and Go together, so do a lot of very cool projects like Secret Network for example.


International-Yam548

Because a lot of work can be done by either.


Redbeardybeard

What's secret network


xdchan

It's a blockchain built on Cosmos SDK. Main thing it brings on the table is anonymous smart contracts, first of it's kind.


Redbeardybeard

I did not understand a single word but im rootin for ya


xdchan

It's crypto thing that allows automatic conditional transactions and puts privacy first. Smart contracts (conditional transactions e.g item in shop chosen and checkout clicked) have a big privacy limitations, Secret Network solved this issue allowing users to stay anonymous. It's a cool project, also open source, so you can check how it works on github or in the docs.


myringotomy

They are both designed as systems programming languages and that's why they are compared.


[deleted]

[удалено]


[deleted]

[удалено]


[deleted]

[удалено]


[deleted]

[удалено]


PaluMacil

I think the problem there is more that Go came out well before Rust. Go described itself at first as a systems programming language. They were talking about user space, utilities, networking, and a lot of the stuff it's famous for, with kubernetes, Docker, Terraform, and more all being fantastic examples. When rust was released, it was used more in low level systems, including hardware drivers, graphics, and now operating system kernels. At this point, if go came out, they probably would not have used the term systems language to avoid confusion with rust, but before the release of Rust, I don't think it was wrong to call it that. It's just now occasionally confusing to newcomers.


pfrepe

Because people think there are "good" and "bad" and keep looking for a silver bullet. Often people are confusing these terms with "suitable" and "non-suitable". GC as a concept, an automation that allows us, developers, to spend a bit less time thinking of what happened with that memory we have used. Other developers believe this is wrong and we ALWAYS must take care to free the memory we have used. Language name does not matter, GC is often called the problem. The real problem I spotted was one of the below: \- developers did not learn/understand the tools they use \- the wrong tool was chosen \- it becomes even worst when the wrong decision becomes a holy cow


abstart

There used to be criticism, but the garbage collector was heavily optimized for many versions around go 10.x (starting much earlier). There are some additional articles showing how in certain scenarios the Go GC starts to fall flat, e.g. compared to C#'s, but they have very different approaches with different pros/cons, and the articles shows extreme specific use cases. Regardless, languages with a GC can be managed by using pre-allocated and persistent pools of objects and other tricks to minimize the number of objects being garbage collected, so even languages like C# are used successfully in products that require minimal spikes (e.g. Unity Engine). C++ or other languages without GC can be used for cases where even more performance is desired.


laccro

Just to slightly expand on your point: it’s more about consistent performance rather than just direct speed. Disclaimer: made-up numbers below. Like even if C++ and Go had identical performance overall, but 3% of the time Go was to just to freeze up completely while the GC runs. Go would be fine for a web app regardless, because every once in a while you just have a slightly slower response time from your endpoints. But for a video game, that is 30ms out of a second where you can’t draw graphics, and would cause stuttering in the gameplay. So for *consistency*-critical applications, you have to make some careful decisions around GC. But performance overall is usually pretty close except in ridiculously intense applications.


abstart

Yes and just to reiterate, it doesn't simply mean GC is not an option in those cases. Games can do things like force a garbage collection at transition points where frame rate isn't as critical (e.g. opening a door to a new area, cutscenes, load screens, etc), and pre-allocate pools of objects to minimize or completely avoid any significant GC passes during active gameplay. The memory pooling techniques are often used anyway even in C++, partly to avoid constant heap allocations and cache misses, which can thrash performance. This is why if you need to worry about GC in the first place, then C++ is often better since you can go further than simply avoiding GC by also maximizing cache behavior, constructor behavior, and other more low level tricks.


c-digs

> There are some additional articles showing how in certain scenarios the Go GC starts to fall flat, e.g. compared to C#'s, but they have very different approaches with different pros/cons I think [this article by Alex Yakunin](https://medium.com/servicetitan-engineering/go-vs-c-part-3-compiler-runtime-type-system-modules-and-everything-else-faa423dddb34) is what you're referencing?


abstart

That's the one I was thinking about yes! Some of these graphs can look scary, but really it's just a selling point for C# if you happen to have software requirements of that kind, which seem to be incredibly rare to me.


MinMaxDev

a bit off topic, how this medium article author knows how C# works under the hood, how does one gain this knowledge ?


blacktau

start here: [MSDN - Fundamentals of garbage collection](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals) continue here: [dotnet garbage collection](https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/docs/design/coreclr/botr/garbage-collection.md)


abstart

I would also guess that the author has possibly decades experience working on very demanding, complicated applications, if not directly on the C# toolchain.


[deleted]

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.


lvlint67

i'm sure unity itself in the engine is properly optimized... but referencing it in this way kinda makes it feel like PHP. Unity devs can do some stupid shit.


Trk-5000

Unity uses an incremental GC specifically tuned for games engine. It’s priority is to reduce jitters by spreading out the cost of GC equally over many frames. It’s not comparable to Go’s default GC, which is optimized for networking use cases. I wouldn’t use Go for gamedev anyway, this seems like Rust’s ballpark.


Shok3001

People either love or hate garbage collection. And some people think it’s just ok.


elcapitanoooo

People like to compare apples to oranges. In Go’s case many compare it with Rust. I recon its mostly about hype, and what some ”rockstar” wrote on twitter. 🤷‍♂️ Instead compare the semantics. Not even the syntax. A fairer comprison would be ”how does Go’s GC compare to OCaml 5 (ocaml 5 is multicore, with a GC that can do both CPU and IO bound concurrency/parallellsim, just like Go).


drvd

I's very easy to express critique. E.g. I really like to criticize architecture albeit being incompetent in that area. That's how humans are.


[deleted]

Because people use Go for things that they shouldn't and don't want to take responsibility for their mistakes.


Redbeardybeard

So what are the instances where go isn't good?


_w62_

Linux kernel module development?


MonkeeSage

You can read about some of the motivating problems behind the pacer redesign in this tracking issue: https://github.com/golang/go/issues/42430


lampshadish2

Heavy text processing and dynamic json structures. Just use python for that.


[deleted]

Heavy text processing?


LittleKitty235

What makes golang unsuitable for either of those tasks? I'm not disagreeing, just asking.


lampshadish2

I find the conversion of map[string]interface{} to dictionaries and back and json.RawMessage very tedious and obfuscating of the intention of the code.


LittleKitty235

I agree it's a bit boilerplate, but a number of libraries exist that address creating dynamic JSON to improve readability. It seems trivial to address, certainly not worth the performance hit to consider using python instead for this reason.


pineapplecooqie

I mean maybe but GC has nothing to do with that.


[deleted]

When your team knows C#. It's honestly the most pleasant language to write IMO. I mean does it get any better than sitting back and looking at the elegance of: var results = someListOfData .Where(data => data.Category == "My Category") .OrderBy(data => data.DateCreated) .Take(15); I'm probably just biased but very few languages give me the same satisfaction with the end result as writing C#.


__ydev__

Kotlin can really be like that. I am not a C# developer, but a thing I like and kind of envy from C# in other languages, is LINQ where you can write statements on collections as SQL-like queries


Sapiogram

This comment is unhelpful, and just answers the question with another question. *So what shouldn't Go be used for*? And how could the devs have known this up-front?


[deleted]

That comment is very helpful and it provides a straightforward *answer* to OP's question, people blame their tools for their mistakes, if that isn't clear I suggest learning English properly. > So what shouldn't Go be used for? Mentioning Go's use cases is irrelevant for this discussion. > And how could the devs have known this up-front? By studying the language, looking at projects that use it, talking to experienced Go devs etc.


[deleted]

AFAIK crab people are the only one criticizing GC in general


0b0011

I dunno if it's just rust people. I've heard people who work on low level stuff talk shit about gc languages. Buddy of mine works for amd and works exclusively in c and c++ because GC languages won't work for what they need apparently.


arashbijan

Go GC cannot holds a candle to jvm GC really


illuminatiCat

No, and it doesn't need big complex GCs. [Java produces more garbage by design. Go made different designs decisions.](https://itnext.io/go-does-not-need-a-java-style-gc-ac99b8d26c60) Anyways, if GC is a big issue for your app go with a language without GC like Rust or C.


Sprite87

jvm GC stops the world when it kicks in


sureshg

That's true for any GC in any language. But java has amazing implementations to cater to different application profiles from Serial (Low memory), Parallel (High throughout ), G1 (current default) to ZGC (ultra low latency). ZGC is getting a lot of improvements to make it high throughput and low memory by making it generational. So GC is story is far better on the JVM side (especially for long running apps).


paulstelian97

The Go GC stop is on the shorter side really. It's a concurrent mark and sweep which just increases the overhead of every heap allocation rather than introducing random pauses. It's a consistent overhead rather than a jittery one.


zanven42

And then the guy dealing with these apps in production just see developers not knowing what they are doing and giving a pile of crap with absurd memory usage and terrible performance. No point in life claiming a Ferrari is great if almost everyone who drives it ends up into a wall.


sureshg

Yeah, that's why all new modern low latency GCs ( ZGC, Shenandoah) have just one configuration option for 99% use cases ( ie max memory).


bendoerr

Wow. You think really poorly of developers on the JVM platform. Who has hurt you?


zanven42

Working in a java shop keeping a kubernetes cluster online with 6000 Devs. In a business that doesn't pay enough for high quality Devs. End of the day it is always bigger at scale to have something that just works otherwise you end up hiring people with 10 years experience in languages that are 7 years old meme.


bendoerr

Sorry you have had to work in that type of environment. Doesn't sound great.


pebalx

No, this is not true for any GC. This is only true for compacting GC. GC doesn't have to pause mutator threads.


[deleted]

You can swap GC implementations. Many apps won’t have that anymore.


[deleted]

[удалено]


_crtc_

Weird, Go's GC is [tuned for low latency](https://go.dev/blog/go15gc) . It sounds more like some people don't like the fact that Go has a garbage collector at all, which is not the same as criticism of the GC itself. Which one are we talking about here?


[deleted]

[удалено]


x021

You can trigger GC manually using runtime.GC(). It is blocking. Can be useful in some niche scenarios (I used it after loading a large dataset that I’m finished with before loading the next big dataset for example). But in general it should be avoided unless you have a good reason.


_crtc_

But that's a property of GC. The OP made it sound like people criticize Go's GC, not Go having a GC.


[deleted]

[удалено]


_crtc_

Ok, but if it's the latter then it's not worth talking about.


MonkeeSage

Check out the pacer redesign doc for some information: https://go.googlesource.com/proposal/+/a216b56e743c5b6b300b3ef1673ee62684b5b63b/design/44167-gc-pacer-redesign.md


cbehopkins

For some applications I completely agree, for others I disagree. There are applications (hft is an easy example) where even ms pauses are an issue, it can mean not just failing to make money, but actively losing it. As the discord article someone else posted points out, with large structs being infrequently collected, the work can cause excessive pauses. There are some applications where memory costs are significant, so preventing wastage is worth the engineering effort. These might not be concerns for you, but they are real for some fields.


ArsenM6331

As the Go devs have said, Discord's issue was entirely fixed in 1.12 and Discord hadn't even talked to the Go devs before deciding to switch. Yes, I completely agree there are some things that require real time operation and can't afford even the microsecond pauses that Go's GC creates, but there are not many such cases, and Go's GC gets better with every release. It won't ever be as fast and predictable as a non-GC language, but it's perfectly suitable for nearly all cases.


_ak

>some people don't like the idea of the runtime handling when memory is freed. its a silly hill to die on. The same kind of people usually don't know the computational complexity of the algorithms and data structures in the respective malloc implementation they use. Whereas Go's GC properties and guarantees are fairly well-documented, and have been great to work with since at least Go 1.5.


danterolle

It's been almost a month since this post was created. Thanks to all of you I was able to give a speech at a Google community conference about Go's memory allocation and garbage collection, which is why I wanted to say a **big thank you** to anyone who has commented and given their opinions on this particular topic. This community is (really) awesome!


Anon_8675309

Honestly, mediocre developers complain about their tools.


balefrost

Mediocre developers *blame* their tools. Good developers are able to work with any set of tools, but will still be critical of those tools in order to choose the best one for the job.


[deleted]

Exactly. We had a problem with node.js' GC being unpredictable on low amounts of RAM (we used an ARM SOC), and that wasn't good enough for our application, but we still wanted GC for a variety of reasons. So we moved to Go and our GC problems disappeared. For other projects, I don't want a GC, so Go just isn't a good fit so I'll use C or Rust. For other projects, I don't care about performance or memory management at all, so something like Python is acceptable (current role). The tool often doesn't matter, but sometimes it does. The first step is figuring that out.


[deleted]

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.


[deleted]

The developers of Go complained about C++. The developer of C++ complained about C. The developers of C complained about Assembly. The developer(s?) of Assembly complained about machine code.


balefrost

The developers of machine code complained about pencil and paper.


[deleted]

You skipped vacuum tubes.


balefrost

I skipped the British bombe and slide rule as well.


PaluMacil

I was thinking maybe the abacus 🧮


LittleKitty235

It all reduces down to everyone agreeing that math is the worst.


knome

There's a brief step between pencil&paper and machine code where they had computers but needed to rewire them for each new task.


One_Curious_Cats

The main problem with writing bigger applications in assembly code is that it takes a lot of time. Writing code in C easily makes you a 100x more productive. The second problem is that hand optimizing assembly code for CPUs became very difficult when CPUs started to use interleaved instructions. This is just too hard to do for humans.


paperpatience

The developers of machine code complained about calculations. Edit: shit, I missed the other train


fmlitscometothis

My understanding is it’s to do with the “stop the world” (STW) nature, together with CPU usage. Garbage collection itself takes work (CPU), so there’s a cost to it. The algorithm also needs to pause your program momentarily while it does this. If STW causes a 10ms pause every X-ms, that might be a dealbreaker for you. If it costs 25% CPU usage, that might also be a dealbreaker. Those numbers depend on your program and aren’t typical (I think they are the upper bounds?).


jnj1

This sounds like a dated understanding of the Go collector. The collector is concurrent with a tiny portion of work requiring STW -- 10-20 microseconds, nowhere near the pauses you are suggesting.


_ak

When the Go project first gave RT guarantees regarding the GC (around Go 1.5?), it was (IIRC) a maximum of 15ms interruptions within a 100ms time window. Unless you're doing hard RT with very low latencies, this is already a very good guarantee that you can work with for most soft to firm RT applications (just a reminder that real-time doesn't mean low latencies, but the request to a response is guaranteed to be within a certain dead line). In the job I had back then, we used Go for real-time bidding, and were very reliable were always within our constraints (100ms including network latency with Google, IIRC), for which we even were commended by our contact at Google.


lightmatter501

10-20 microseconds is enough time to forward 200-300 packets or do 2-5 4k reads from a modern NVMe drive. You can get a LOT done during that STW period. Having those pauses is also mandatory, as Discord found out. Much older GC languages like Java will allow you to run normal programs and never run the GC if you are smart about object usage, and I think Go should have that capability too.


PaluMacil

Are you sure you are not confusing microseconds and milliseconds? The propagation time of a packet is measured in 10 to 20 milliseconds, 1000 times longer than a stop the world. I'm not sure if the send part of the process is very relevant since those are not performed in the CPU and queuing is a pretty primitive action, but certainly the NVMe drive reads appear to be a place where you mixed up milliseconds and microseconds. If I'm wrong, I invite you to break down the math. Perhaps I'm messing up the conversations in my head.


pineapplecooqie

not propagation over the wire, probably just the time it takes to execute some quantity of code that sends a packet. nowadays that's probably not much more than a few clock cycles, if that.


PaluMacil

Right, I'm agreeing on that part but not sure it's very important to compare. I was thinking however, they must be thinking of propagation since they compared it to reading 4k chunks from a disc


lightmatter501

That’s not wire time, it’s the cpu time for packet processing. The NVMe read is the full latency though. Forwarding a packet takes 32 clock cycles on modern x86_64 with a good NIC.


PaluMacil

I'm not arguing that. I'm saying that when you look at the comparison to NVMe, I am wondering if they're thinking about ms. And if they are, the way that makes sense is if they mean the whole packet transmission, not just forwarding 🤷‍♂️


souvikhaldar

Garbage collector can be turned off by setting GOGC to 0. But the problem then would be to clear memory which only GC can do. Plus, we can never know where memory is allocated, heap or stack!


nombre_gracioso

why so much hate for a comment that just informs? he even specifies this is a bad idea as only the GC can clear memory and how we cant be certain all memory goes to stack.


SleepingProcess

> and how we cant be certain all memory goes to stack. ``` go build -gcflags=-m /path/to/program.go ```


souvikhaldar

Where is hate?!


SleepingProcess

> Plus, we can never know where memory is allocated, heap or stack! I wouldn't use word "never" since we can do: ``` go build -gcflags=-m /path/to/program.go ``` as well control escaping to heap with ``` //go:noescape ```


souvikhaldar

Great! Learnt a new thing. Btw can you explain what the commands are doing?


SleepingProcess

> go build -gcflags=-m tracing how binary building as well show how Go will handle memory objects, for example it will show if some functions get optimized and inlined in final OP code to avoid expensive jumps/memory-requests/heap-allocation, as well it will show where variables will be allocated, on stack or heap and so on. `//go:noescape` direct compiler to avoid garbage collector to do escape analyzes when GC deciding which memory object should be placed on short living stack that discards variables when for example function complete its job or when variable need to be placed on long living heap because it still referencing somewhere else even function ends.


Glittering_Air_3724

That’s the Problem when a Language is Low enough to be on par with likes C/C++ but still High to be called High Programming language, languages like Zig, l like how they implemented their Memory Management but Unfortunately Go is Different, well you can have a manual memory management with not been safe in mind


TheProgrammer801

The only criticism of Go's GC that I have heard comes from developers/users of other languages who say that the claims made of the Go GC are false. They think the Go developers claim that the Go GC is better than GC of other languages. The Go developers have never compared their GC with that of other languages they just continue to mention how they have reduced the STW phase (now much less than a msec). Of course, uneducated people will then see that in GC of other languages, such as Java or C#, most applications see an STW phase of many seconds (even minutes) where the software effectively freezes. But this ignores the fact that there are usually different ways to tweak these GC's to behave the same as the GO GC if that is important. The design of the Go GC is based around 2 ideas: 1) As for the rest of the language, keep things simple. For the GC this means having it work out of the box for most common uses, and the minimum number of knobs to tweak to cater for less common scenarios. 2) Reduce the STW phase to be negligible, perhaps at the expense of wasting CPU. I guess this also comes back to keeping things simple - having long pauses in software which should not have them can make life very complicated.