Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

C was wildly successful because the world needed a language that had some of the mechanisms of high level languages, which allowed low level control and compiled to fast machine code. C hit an in-between niche at the right time and place.

As far as I can see, Go is doing the same. We need a language that has some mechanisms of higher level languages, like CSP style concurrency and GC, but which still allows for low level control of things like memory layout.

I ported a partly done multiplayer game server written in Clojure to Go, and I find that I'm more productive in Go. The tooling and server monitoring is more developed for the JVM platform, but coding in Go is more fun because everything is immediately responsive, and nothing "falls over" like it can with nREPL/bREPL with Clojure/ClojureScript. Always paying that several seconds environment startup delay, or the additional management required to avoid it, does wear on you in the long run.



C was successful, because it was required to target UNIX, like JavaScript is required to target the Web, Objective-C is required to target iOS and so on.

There were plenty of languages with similar capabilities at the time. Algol, PL/I, Mesa, ....


I didn't say C was only successful because of its middle-approach. It was also in the right place at the right time. I'm sure being from Google is helping Golang tremendously.


I'm curious, what other reasons did you have to switch from Clojure to Go? I love coding in Clojure, so I wonder what would make someone "like me" switch.


When going for parallelism, it's helpful to be able to lay out what goes where in memory to avoid false sharing. This is quite hard to do in Clojure. The JVM has better GC, but Go also allows one to avoid GC.

Also, to write fast code, I don't have to decompile bytecodes or do obscure things to make sure arithmetic is what I meant it to be. Int64 addition is int64 addition.

Some things are definitely more powerful in Clojure. But that isn't where my priorities lie right now.


> The JVM has better GC, but Go also allows one to avoid GC.

I don't see that Go allows you to avoid GC any more than Java does, really. You can (and Java programmers do, with regularity) use things like sync.Pool in Java as well.


Golang tends to need far fewer allocated objects than Java.

You can allocate arrays (and slices) of struct in golang. Just one allocated object irregardless of how many items there are. In Java, you need array of objects and an instance of each object. Array of 100 objects needs 101 allocations in Java. So one could say golang avoided gc for those 100 objects it didn't need in the first place.

In Java, you're otherwise limited to arrays of elementary types.


> So one could say golang avoided gc for those 100 objects it didn't need in the first place.

That's a reduction of the number of allocations, not of GC. In a generational copying GC like that of Java, the GC runs a minor collection when you've allocated enough bytes in the nursery to fill it up; it doesn't matter whether those bytes came from 1 object or from 101 objects. From reading Go's documentation [1] it's unclear whether its GC collection cycle is triggered based on the number of objects or whether it's triggered based on memory usage, but I assume the latter as that's how most GCs work.

The pressure on the GC during the mark phase is based on the number of total pointers. You may be able to reduce the total number of pointers by packing pointer-free structs together, but I would be surprised if this helps mark performance that much in practice.

The main way to really reduce GC pressure in a fully GC'd system, short of improving the GC itself, is to use pooling. Which both Java and Go programmers do regularly.

[1]: http://golang.org/pkg/runtime/debug/#SetGCPercent


GC object graph.

Golang case: 1 object, no child nodes. Nothing to do. Code that does not need to run is the fastest.

JVM case: visit array object and its 100 child nodes. JVM's GC still needs to visit all nodes of the graph. What actually takes time in GC is all those L1/L2/TLB misses and extra page faults caused by following the object graph. If all objects happen to be on a different cache line, L1 spills happen after just 512 references on Intel Sandy Bridge, Ivy Bridge, Haswell, etc. (and sooner in reality). Those extra loads from L2 are not free.

So, in this case Golang needed to visit 1 struct (object). JVM needed to visit 101 objects.

I wasn't talking about object lifetimes at all. I was talking about memory layout and point out in golang you can have the objects sequentially in memory without need for pointer indirection (references).

Thus generational GC doesn't have anything to do with this. Gen GC just something nice to have when a limited call graph generates a lot of temporary objects, i.e. garbage.


I mentioned this in the second paragraph. Like I said, I'd be surprised if that helps mark times that much in practice. For minor collections in a generational GC you're typically doing a Cheney scan, so it's very unlikely to matter as you're copying the whole live region of the semispace anyhow. For major collections on tenured objects, in theory it could help, but again I'm skeptical that it will affect mark performance that much, because compaction does an excellent job of mitigating the cache effects. (It's impossible to accurately measure this stuff right now, as the fact that Go's GC is much more immature than the HotSpot GC will skew the numbers.)


Here's an explanation from Russ Cox (in the form of an SO answer) into how Go and Java differ in terms of object allocations and control of memory layout. http://stackoverflow.com/a/22214673/1567738

I'm sure there's more info in the golang-dev group (https://groups.google.com/forum/#!forum/golang-dev) related to the GC specifics, but it's a moving target and may change substantially in the next few versions.


The main way to really reduce GC pressure in a fully GC'd system, short of improving the GC itself, is to use pooling.

It occurs to me, that I don't understand how pooling helps a non generational GC like golang's. In a generational collector, the contents of the pool would be promoted to regions of GC memory that are copied less. Go's GC isn't generational, so what is going on?


If you use a pool, you can explicitly return memory to it without going through the GC. This causes mark/sweep cycles to occur less often. (Of course, using pools opens you up to use-after-free and memory leaks, albeit without the type- and memory-safety consequences of use-after-free in C/C++.)


If you use a pool, you can explicitly return memory to it without going through the GC. This causes mark/sweep cycles to occur less often.

In a non-generational collector, why less often? Is it that the GC "sees" less garbage, and this figures into the frequency of mark/sweep cycles?

EDIT: Okay, I just got it. If you assume it's a bump allocator, it's easiest to picture. So Go does have another big advantage with regards to reducing GC pressure, in that one can stack allocate structs and arrays. (One would be passing slices on those arrays most often, and stack allocated would be slightly less flexible, of course.)


Right. See the documentation here [1]. Typically GCs run on allocation when they see a high ratio of the live set from the previous run to total memory in use.

[1]: http://golang.org/pkg/runtime/debug/#SetGCPercent


I did a piece on using Clojure with Go recently. For me, it wasn't a switch but using them along side each other to tackle different problems more efficiently. Here's the link -- http://www.quantisan.com/simple-easy-quick-using-go-along-wi...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: