Live data from Hacker News

Go Optimization Guide

goperf.dev

141–150 of 173 posts

Re: Go Optimization Guide

#141

Every perf guide recommends to minimize allocations to reduce GC times, but if you look at pprof of a Go app, GC mark phase is what takes time, not GC sweep. GC mark always starts with known live roots (goroutine stacks, globals, etc) and traverse references from there colouring every pointer. To minimize GC time it is best to avoid _long living_ allocations. Short lived allocations, those which GC mark phase will ne…

Interesting, and I think that is not specific to Go, other mark-and-sweep GCs (Java, C#) should behave the same.

Which means that creating short lived objects (like iterators for loops, or some wrappers) is ok.

Re: Go Optimization Guide

#142
post #82

Every perf guide recommends to minimize allocations to reduce GC times, but if you look at pprof of a Go app, GC mark phase is what takes time, not GC sweep. GC mark always starts with known live roots (goroutine stacks, globals, etc) and traverse references from there colouring every pointer. To minimize GC time it is best to avoid _long living_ allocations. Short lived allocations, those which GC mark phase will ne…

Side note: see https://tip.golang.org/doc/gc-guide for more on how the Go GC works and what triggers it. GC frequency is directly driven by allocation rate (in terms of bytes) and live heap size. Some examples: - If you halve the allocation rate, you halve the GC frequency. - If you double the live heap size, you halve the GC frequency (barring changes away from the default `GOGC=100`). > ...but if you look at pprof…

Interesting, thank you. But I think those points are not correlated that much. For example if I create unnecessary wrappers in a loop, I might double the allocation rate, but I will not halve the live heap size, because I did not have those wrappers outside the loop before.

Basically, I'm trying to come up with an real world example of a style change (like create wrappers for every error, or use naked integers instead of time.Time) to estimate its impact. And my feeling is that any such example would affect one of your points way more than the other, so we can still argument that e.g. "creating short-lived iterators is totally fine".

Re: Go Optimization Guide

#143
post #31
post #19

Earlier quoted context omitted.

No. If you have a moving multi generational GC, allocation is literally just an increment for short lived objects.

If you have a moving, generational GC, then all the benefits of fast allocation are lost due to data moving and costly memory barriers.

It’s in uncharitable to say the benefits are lost - I’d reframe it as creating tradeoffs.

Re: Go Optimization Guide

#144
post #92

Earlier quoted context omitted.

Go did use to do that, it was removed years ago, in 1.4: https://go.dev/doc/go1.4#runtime

Basically, anything that isn't a thin pointer (*T, chan, map) gets boxed nowadays. The end result is that both words of an interface value are always pointers [1], which is very friendly to the garbage collector (setting aside the extra allocations when escape analysis fails). I've seen some tricks in the standard library to avoid boxing, e.g. how strings and times are handled by log/slog [2]. [1]: https://github.com…

slog.Value looks incredibly useful.

Just imagine a day where database/sql doesn't generate a tonne of garbage because it moves to use something like that?

Re: Go Optimization Guide

#145

Every perf guide recommends to minimize allocations to reduce GC times, but if you look at pprof of a Go app, GC mark phase is what takes time, not GC sweep. GC mark always starts with known live roots (goroutine stacks, globals, etc) and traverse references from there colouring every pointer. To minimize GC time it is best to avoid _long living_ allocations. Short lived allocations, those which GC mark phase will ne…

The runtime also forces GC every 2 minutes. So yeah, a lot of long living allocations can stress the GC, even if you don't allocate often. That's why Discord moved from Go to Rust for their Read States server.

Re: Go Optimization Guide

#146

Every perf guide recommends to minimize allocations to reduce GC times, but if you look at pprof of a Go app, GC mark phase is what takes time, not GC sweep. GC mark always starts with known live roots (goroutine stacks, globals, etc) and traverse references from there colouring every pointer. To minimize GC time it is best to avoid _long living_ allocations. Short lived allocations, those which GC mark phase will ne…

Interesting, and I think that is not specific to Go, other mark-and-sweep GCs (Java, C#) should behave the same. Which means that creating short lived objects (like iterators for loops, or some wrappers) is ok.

It should be noted that in C#, at least, the standard pattern is to use value types for enumerators, precisely so as to avoid heap allocations. This is the case for all (non-obsolete) collections in the .NET stdlib - e.g. List.Enumerator is a struct.

Re: Go Optimization Guide

#147
post #96

Earlier quoted context omitted.

It is fairly common your type system ends up with escape hatches allowing you to violate the type rules in practice. See e.g., OCaml and the function "magic" in the Obj module. It serves as a way around a limitation in the type system which you don't want to deal with. You can still have the rest of the code base be safe, as long as you create a wrapper which is. The same can be said about having imperative implement…

Obviously every type system in practice has escape hatches. But I’ve never seen another staticly-typed language where you need to break out of the type system so regularly. Go’s type system has your back when you’re writing easy stuff. But it throws up its hands and leaves you to fend for yourself when you need to do nearly anything interesting or complex, which is precisely when I want the type system to have my bac…

> But I’ve never seen another staticly-typed language where you need to break out of the type system so regularly.

It's about the same as Java and C# prior to their adoption of generics, and largely for the same reasons.

Re: Go Optimization Guide

#148
post #120
post #66

Earlier quoted context omitted.

> Not in the slightest. You know, it's this kind of comment on USENET forums which prompted the creation of StackOverflow. It's not curious and adds nothing to the discussion. I like Go and use it extensively; and I like having the option to fall back to the `any` type. But it's simply a fact that using the `any` type means that certain properties of the program can't be checked at compile time, in the same way that…

> But it's simply a fact that using the `any` type means that certain properties of the program can't be checked at compile time Yes, structural typing removes the ability to check certain properties at compile-time. That doesn't make it typed like Python, though.

"any" is not structural typing.

Re: Go Optimization Guide

#149
post #95
post #50

Earlier quoted context omitted.

You never programmed in Go, I assume? Then you have to understand that the type of `pool.Get()` is `any`, the wildcard type in Go. It is a type, and if you want the underlying value, you have to get it out by asserting the correct type. This cannot be solved with generics. There's no way in Java, Rust or C++ to express this either, unless it is a pool for a single type, in which case Go generics indeed could handle t…

> There's no way in Java, Rust or C++ to express this either You make it look like it's a good thing to be able to express it. There's no way in Java, Rust or C++ to express this, praised be the language designers. As for expressing a pool value that may be multiple things without a horrible any type and an horrible cast, you could make an union type in Rust, or an interface in Java implemented by multiple concrete o…

> There's no way in Java, Rust or C++ to express this, praised be the language designers.

That's not even the case. In Java, you'd just use Object, which is for all practical purposes equivalent to `interface{}` aka `any` in Go. And then you downcast. Indeed, code exactly like this was necessary in Java to work with collections before generics were added to the language.

In C++, there's no common supertype, but there std::any, which can contain a value of any type and be downcast if you know what the actual type is.

Re: Go Optimization Guide

#150
post #87

Earlier quoted context omitted.

Because the order of fields can be significant. It's very relevant for syscalls, and is observable via the reflect package; it'd be strange if the field order was arbitrarily changed (and might change further between releases). I assume the thinking was that this is pretty easy to optimise if you care, and if it's on by default there'd then have to be some opt-out which there isn't a good mechanism for.

In particular, struct field alignment matches C (even without cgo) and so any change to the default would break a lot of code.

This is very unfortunate, since most structs are never going to be passed to C, yet end up paying the tax anyway. They really should have made it opt-in.
Post reply on HN