英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论