Hacker News new | ask | show | jobs
by fdr 5085 days ago
I've been dealing with this in quite a bit of detail in the last week. The answer is: it is entirely possible to write a Go program (or sub-programs, which is even better: use GC when you need it) that produces absolutely no garbage, and it doesn't require being a seer to do so. It requires a bit more seer-ness to leverage the fairly simpleminded escape analysis to make some things more terse, but that's a fairly small workflow optimization.

The biggest hurdle I've had is that the standard library can make it difficult to avoid allocation: for example, I want to re-initialize a "bytes.Reader" or "bytes.Buffer", but I can't just pass a new slice and reset the data structure fields, from what I know. As a result, I've had to do the somewhat unpleasant task of pulling in standard library files and then tweaking them, or writing new abstractions to do some low level stuff entirely -- perhaps unavoidable, it is probably better to have a nice interface for general cases from a standard library perspective.

It is worse when you realize that a standard library function calls "make" in a place where it'll be in your inner loop: you really wish you could pass the memory to scribble into instead.

Another surprisingly expensive thing is the dynamic dispatch on interfaces. This is easy to avoid if one just writes a specialized version of a function accepting all the concrete types in the common case, though. Beyond that, memory copying, something I've found that the bytes.Buffer and bytes.Reader make hard to avoid while still capturing their useful semantics.

All in all: more pleasant than C for most projects, and very nearly as fast when like this generally, so even if you end up having to write a good chunk of stuff yourself you are no worse off than if you had chosen C to begin with.

A very notable caveat: you can't handle in any way (afaik) failed requests for virtual memory, which is rather a killer for some kind of programs. This one makes me a bit annoyed, but I can see why it's difficult to address. That doesn't make me feel much better, though.

To give a sense of what I'm doing: I'm parsing very small messages (minimum: 5 bytes, and often quite small, a common pathological case would be a handful more bytes) and passing them on with no interesting processing, and trying to get this to be fast in the trivial case. This is the Postgres wire protocol. So far, Postgres is still beating the crap out of me, which is annoying considering it actually has to do some work, and I'm just inspecting one byte and one 4-byte word of the message and then passing it on. At this point, with two copies the program is ~20% runtime.memmove, so the next high pole is to eliminate one of the copies.