Earlier quoted context omitted.
Why would Pool increase memory usage?
Let's say you have constantly 1k requests per second and for each request, you need one buffer, each 1 MiB. That means you have 1 GiB in the pool. Without a pool, there's a high likelihood that you're using less. Why? Because in reality, most requests need a 1 MiB buffer but SOME require a 5 MiB buffer. As such, your pool grows over time as you don't have control over the distribution of the size of the pool items. S…
Go Optimization Guide
91–100 of 173 posts
Re: Go Optimization Guide
#92Earlier quoted context omitted.
I thought surely an integer could be inlined into the interface, I thought Go used to do that. But I tried it on the playground, and it heap allocates it: https://go.dev/play/p/zHfnQfJ9OGc
Go did use to do that, it was removed years ago, in 1.4: https://go.dev/doc/go1.4#runtime
[1]: https://github.com/teh-cmc/go-internals/blob/master/chapter2...
[2]: https://cs.opensource.google/go/go/+/refs/tags/go1.24.1:src/...
Re: Go Optimization Guide
#93Earlier quoted context omitted.
> That's what the New function does, isn't it? But that's only run when the pool needs to allocate more space. What GP seems to expect is that sync.Pool() would always return a zeroed structure, just as Golang allocation does. I think Golang's implementation does make sense, as sync.Pool() is clearly an optimization you use when performance is an issue; and in that case you almost certainly want to only initialize pa…
> What GP seems to expect is that sync.Pool() would always return a zeroed structure, just as Golang allocation does. One could define a new "Pool[T]" type (extending sync.Pool) to get these guarantees: type Pool[T any] sync.Pool // typed def func (p *Pool[T]) Get() T { // typed Get pp := (*sync.Pool)(p) return pp.Get().(T) } func (p *Pool[T]) Put(v T) { // typed Put pp := (*sync.Pool)(p) pp.Put(v) } intpool := Pool[…
Re: Go Optimization Guide
#94Earlier quoted context omitted.
> That's what the New function does, isn't it? But that's only run when the pool needs to allocate more space. What GP seems to expect is that sync.Pool() would always return a zeroed structure, just as Golang allocation does. I think Golang's implementation does make sense, as sync.Pool() is clearly an optimization you use when performance is an issue; and in that case you almost certainly want to only initialize pa…
> What GP seems to expect is that sync.Pool() would always return a zeroed structure, just as Golang allocation does. One could define a new "Pool[T]" type (extending sync.Pool) to get these guarantees: type Pool[T any] sync.Pool // typed def func (p *Pool[T]) Get() T { // typed Get pp := (*sync.Pool)(p) return pp.Get().(T) } func (p *Pool[T]) Put(v T) { // typed Put pp := (*sync.Pool)(p) pp.Put(v) } intpool := Pool[…
So long as that one is not you? You completely forgot to address the expectation:
type Foo struct{ V int }
pool := Pool[*Foo]{ // Your Pool type.
New: func() any { return new(Foo) },
}
a := pool.Get()
a.V = 10
pool.Put(a)
b := pool.Get()
fmt.Println(b.V) // Prints: 10; 0 was expected.Re: Go Optimization Guide
#95Checking out the first example—object pools—I was initially blown away that this is not only possible but it produces no warnings of any kind: pool := sync.Pool{ New: func() any { return 42 } } a := pool.Get() pool.Put("hello") pool.Put(struct{}{}) b := pool.Get() c := pool.Get() d := pool.Get() fmt.Println(a, b, c, d) Of course, the answer is that this API existed before generics so it just takes and returns `any` (…
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…
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 objects. Both ways would force the consumer to explicitly check the value without requiring unchecked duck typing.
Re: Go Optimization Guide
#96Checking out the first example—object pools—I was initially blown away that this is not only possible but it produces no warnings of any kind: pool := sync.Pool{ New: func() any { return 42 } } a := pool.Get() pool.Put("hello") pool.Put(struct{}{}) b := pool.Get() c := pool.Get() d := pool.Get() fmt.Println(a, b, c, d) Of course, the answer is that this API existed before generics so it just takes and returns `any` (…
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 implementations with functional interfaces wrapping said implementation. From the outside, you have a view of a system which is functionally sound. Internally, it might break the rules and use imperative code (usually for the case of efficiency).
Re: Go Optimization Guide
#97Earlier quoted context omitted.
I thought surely an integer could be inlined into the interface, I thought Go used to do that. But I tried it on the playground, and it heap allocates it: https://go.dev/play/p/zHfnQfJ9OGc
Go did use to do that, it was removed years ago, in 1.4: https://go.dev/doc/go1.4#runtime
Re: Go Optimization Guide
#98Every 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…
Agree that mark phase is the expensive bit. Disagree that it’s not worth reducing short-lived allocations. I spend a lot of time analyzing Go program performance, and reducing bytes allocated per second is always beneficial.
Re: Go Optimization Guide
#99Earlier 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…
Re: Go Optimization Guide
#100Earlier quoted context omitted.
Go did use to do that, it was removed years ago, in 1.4: https://go.dev/doc/go1.4#runtime
go1.15 re-added small integer packing into interfaces: https://go.dev/doc/go1.15#runtime
This array is also used for single-byte strings (which previously had its own array): https://go-review.googlesource.com/c/go/+/221979/3/src/runti...