Store net.Conn by value or reference?
My app uses a sync.Map
to store open socket-connections which are accessed concurrently through multiple goroutines.
I'm wondering whether to store these connections as structs net.Conn
or as references *net.Conn
.
What are the benefits/drawbacks of both options and what would be the prefered solution?
Solution 1:
While @blackgreen is correct, I'd expand a bit on the reasoning.
The sync.Map
type is explicitly defined to operate on interface{}
.
Now remember that in Go, an interface is not merely an abstraction used by the type system; instead, you can have values of interface types, and the in-memory representation of such values is a struct
containing two pointers—to an internal object describing the dynamic type of the value stored in the variable, and to the value itself (or a copy of it created on the heap by the runtime).
This means, if you were to store a pointer to anything in sync.Map
, any such pointer stored would have been converted to a value of type interface{}
and it would occupy exactly the same space in sync.Map
.
If, instead, you would store values of type net.Conn
there directly, they would have been stored directly—simply because they are already interface values, so Go would just copy the pair of pointers.
On the surface, this looks like both methods are on par in terms of the space used but bear with me.
To store a pointer to a net.Conn
value in a container data type such as sync.Map
, the program must make sure that that value is allocated on the heap (as opposed to allocating it directly on the stack of the currently running goroutine), and this fact might force the compiler to arrange for ensuring that the original net.Conn
value is allocated directly on the heap.
In other words, storing a pointer to a variable of interface type might be (and usually will be—due to the way typical code is organized) more wasteful in terms of memory use.
Add to it that most dereferencing (pointer chasing) tends to trash CPU cache; that's not a game changer but might add up to a couple of µs when you iterate over collections in tight loops.
Having said that, I'd would advise against outright dismissing storing pointers in containers like sync.Map
: occasionally it comes in handy—for instance, to reuse arrays for slices, you usually store pointers to the 1st elements of such arrays.