将 net.Conn 存储为值还是引用?

huangapple go评论59阅读模式
英文:

Store net.Conn by value or reference?

问题

我的应用程序使用sync.Map来存储通过多个goroutine并发访问的打开的socket连接。

我想知道是将这些连接存储为net.Conn结构体还是引用*net.Conn

这两种选项的优缺点是什么,哪种是首选解决方案?

英文:

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?

答案1

得分: 3

虽然@blackgreen是正确的,但我想进一步解释一下原因。

sync.Map类型明确定义为在interface{}上操作。

现在请记住,在Go语言中,接口不仅仅是类型系统使用的抽象概念;相反,你可以拥有接口类型的值,并且这些值的内存表示是一个struct,其中包含两个指针——一个指向描述存储在变量中的值的动态类型的内部对象,另一个指向值本身(或者是运行时在堆上创建的副本)。

这意味着,如果你要在sync.Map中存储任何指针,那么存储的指针都会被转换为interface{}类型的值,并且它们将在sync.Map中占用完全相同的空间。相反,如果你直接在其中存储net.Conn类型的值,它们将直接被存储,因为它们已经是接口值,所以Go语言只会复制这对指针。

表面上看,这两种方法在使用空间方面是相当的,但请耐心等一下。要在诸如sync.Map之类的容器数据类型中存储net.Conn值的指针,程序必须确保该值在堆上分配(而不是直接在当前运行的goroutine的栈上分配),这个事实可能迫使编译器安排确保原始的net.Conn值直接在堆上分配。

换句话说,在内存使用方面,存储指向接口类型变量的指针可能会更加浪费(通常是由于典型代码的组织方式)。

此外,大多数解引用(指针追踪)往往会破坏CPU缓存;这并不是一个决定性因素,但在紧密循环中迭代集合时可能会累积一些微秒。


话虽如此,我建议不要完全排除在sync.Map等容器中存储指针的可能性:偶尔这样做会很方便——例如,为了重用数组用于切片,通常会存储指向这些数组的第一个元素的指针。

英文:

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.

huangapple
  • 本文由 发表于 2022年1月18日 19:01:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/70754495.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定