Remix.run Logo
tsimionescu 5 days ago

I don't think that's the (only) reason Go became popular. The huge thing about Go is the runtime: it's the only language runtime available today, at least in any language with a large org behind it, that offers (a) GC, (b) fast start-up time, (c) static types, (d) fast execution, and (e) multi-threading.

This is a killer combination for any team looking to write code for auto-scalable microservices, to run for example on Kubernetes. Java is not great in this niche because of its slow startup time, relatively large memory overhead, and the need for warm-up before code actually starts executing fast (so scaling up and down has a very large cost for Java services). .NET has similar problems, and also a huge container size. Python is far too slow, and not typed. TypeScript is single threaded, and still has a pretty hefty runtime. OCaml doesn't have any large org behind it, is quite obscure syntax, and was still single-threaded at the time Kubernetes started. Haskell has similar issues, and is also large and slow starting. Rust, C++, C all require manual memory management.

So, it's no surprise that Go was used for Kubernetes services themselves, and it's no surprise that people designing for Kubernetes mostly chose to write their new stuff in Go. Go the language, with its antiquated design, is actually quite secondary to all of that. But Go's runtime is completely unmatched in this space.

dev_l1x_be 4 days ago | parent | next [-]

F# does that too.

> .NET has similar problems

s/has/had/

https://blog.washi.dev/posts/tinysharp/

The issue is that some people still fighting against the concepts ML family languages (primarily SML) introduced. Go implemented go routines and channels from CSP (https://en.wikipedia.org/wiki/Communicating_sequential_proce...) but dragged a lot on influence from C (understandable) into the language.

I think Rust opted for the best combinations (some CSP, a lot of ML and a bit of C++).

9rx 3 days ago | parent | next [-]

> The issue is that some people still fighting against the concepts ML family languages

To be fair, everyone was fighting against ML concepts at the time. Ruby on Rails was "in" and "doing situps" was "out". Go was built for the time it was created. It was, quite explicitly as told at its launch announcement, made to be a "dynamically typed" language with static type performance. It is unlikely it would have had a static type system at all if they knew how to achieve the same performance optimizations without a type system.

> I think Rust opted for the best combinations

But built in another time. Ruby on Rails was "out" and static typing (ML style in particular) was "in" by the time Rust finally got around to showing up to the party. Looking back, it may not seem like there was much time between the creation of Go and the creation of Rust, but on the tech scale it was created eons later. The fashion of tech can change on a dime — as captured in the humorous fable about JS having a new "must-use" framework every week.

The fashion trends will change again at some point. They always do.

tsimionescu 4 days ago | parent | prev | next [-]

Not sure what you mean about F# - being a CLR language, it has the same runtime issues as C# (and IronPython, managed C++, etc).

The article you quote is a toy example - if you write a C# or F# web API server, you'll see that it takes up way more space than a Go one with similar functionality (and has way higher memory overhead as well). A Go API web server is maybe 10MB on disk, with no dependencies (that is, you can run it perfectly in a container that is defined as `FROM scratch; COPY my-go-exec /my-go-exec `). The equivalent Java or .NET container is somewhere around 2-400MB at the minimum.

As for the syntax and constructs, I don't care so much. If OCaml or SML had comparable support and a comparable ecosystem to Go, I'd bet plenty of people would have chosen them instead.

atombender 4 days ago | parent | prev [-]

OP's point is about why Go took off, which happened more than a decade ago.

Native AOT compilation didn't exist for .NET then, and .NET was, for all intents and purposes, Windows-only.

(Last I checked, the AOT story for C# is not that great even today. No idea about F#.)

qcnguy 4 days ago | parent | prev [-]

Kubernetes services are one of the places where you don't care about startup time. Likewise for Docker itself. These are the things that do the scaling, normally.

Go is not particularly fast. People often see that Java gets faster as it runs and thinks, oh, it must be slow at the start then. But when you compare like with like, Go ends up being stuck at the bottom of the curve that Java then sharply climbs. The difference in GC quality is big, or at least, used to be? The only place where you really notice the difference is command line tools, and Java has GraalVM for that.

tsimionescu 4 days ago | parent | next [-]

> Kubernetes services are one of the places where you don't care about startup time.

There are some kubernetes services that scale up and down. And even for those that don't normally, if they have some kind of failure, the difference between taking a millisecond to get back up and taking a second can actually matter for a web host.

> Go is not particularly fast. People often see that Java gets faster as it runs and thinks, oh, it must be slow at the start then. But when you compare like with like, Go ends up being stuck at the bottom of the curve that Java then sharply climbs.

Go starts up much faster than Java. And Go code runs measurably faster than interpreted Java code, even though it's slower than the JITed code you'll eventually have if your JVM runs long enpigh. But un-JITed Java code is very slow, more comparable to Python than JITed Java or with Go . This has nothing to do with the GC - where I do agree Go is mediocre at best.

zozbot234 4 days ago | parent [-]

I wouldn't call the Go GC mediocre, it's one of the few fully concurrent GC's in common use. It probably has significantly lower memory demand than Java/NET for comparable workloads.

tsimionescu 3 days ago | parent | next [-]

Go's GC is not compacting, it has significant mutator overhead on every pointer write, it has an expensive allocator (it has to search through free lists, since it can't compact), and the free pahse has to do work proportional to the amount of garbage the program produces. It also produces no debugging information for ownership, for some bizarre reason (you can't tell from a Go heap dump with any standard Go tool which object is keeping another object around).

The fact that it can do this mostly concurrently (it's not fully concurrent, actually) is not that special - Java's latest GC is also mostly concurrent, without having any of the other previous drawbacks.

Go's big advantage in terms of memory management is that it doesn't produce as much garbage as most GC languages. The fact that it natively supports value types, and actually uses them extensively in the standard library (unlike C#, which also supports these), and it's excellent stack escape analysis which allows many allocations to stay on the stack and outside the GC heap entirely, is what makes Go have such little memory overhead.

pebal 4 days ago | parent | prev [-]

This isn't fully concurrent GC. It pauses mutators threads and delegates them to perform some of the work for the GC.

atombender 4 days ago | parent | prev [-]

When using HPAs to quickly ramp replica sets up and down based on load, startup time is absolutely an important factor. You want services to start within hundreds of milliseconds at most.