在Go语言中,获取切片元素的地址是否意味着对元素进行复制?

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

Does taking the address of a slice element implies a copy of the element in Go?

问题

假设一个 Go 1.18 程序有一个非常庞大的 struct,需要考虑复制的成本:

type MyStruct struct {
    P string
    // 很多属性
}

现在让我们定义一个函数,它以这样的元素切片作为输入参数,其目标是更新每个切片元素的属性:

func myFunc(sl []MyStruct) {
    for i := range sl {
        p := &sl[i]       // <-- 这里
        p.P = "bar"
        // 其他属性的变化
    }
}

&lt;-- HERE 标记处,Golang 编译器是在循环作用域中创建一个临时副本,还是直接获取切片元素的地址?

这个想法是避免复制整个切片元素。

一个可工作的示例:https://go.dev/play/p/jHOC2DauyrQ?v=goprev

英文:

Let's say a Go 1.18 program has a quite heavy struct, for which copying is to be considered costly:

type MyStruct struct {
    P string
    // a lot of properties
}

Now let's define a function, taking a slice of such elements as input parameter, which goal is to update properties of each slice element:

func myFunc(sl []MyStruct) {
    for i := range sl {
        p := &amp;sl[i]       // &lt;-- HERE
        p.P = &quot;bar&quot;
        // other properties mutations
    }
}

At the &lt;-- HERE mark, is the Golang compiler making a temporary copy of the slice element into the loop's scope, or is it taking the address of the slice element in-place?

The idea is to avoid copying the whole slice element.

A working example: https://go.dev/play/p/jHOC2DauyrQ?v=goprev

答案1

得分: 2

&amp;sl[i]不会复制切片元素,它只会求出第i个元素的地址。

切片元素就像变量一样,&amp;x求出的是变量x的地址。想一想:既然&amp;sl[i]是第i个元素的地址,这个地址不需要也不使用结构体的值,为什么要复制呢?

如果你的切片非常大,担心(隐式)复制的性能影响,你应该考虑一开始就在切片中存储指针,这样你可以使循环和访问元素更简单,而不必担心复制:

func myFunc(sl []*MyStruct) {
    for _, v := range sl {
        v.P = "bar"
        // 其他属性的修改
    }
}

还要注意,如果你的切片保存的是非指针类型,并且你想要修改切片元素的字段,通过索引切片并引用字段也不会复制结构体元素:

func myFunc(sl []MyStruct) {
    for i := range sl {
        sl[i].P = "bar"
        // 其他属性的修改
    }
}

是的,这种方式可能更冗长,如果你需要修改多个字段可能效率也会较低(但编译器也可能识别并优化多个sl[i]表达式的求值)。

英文:

&amp;sl[i] does not copy the slice element, it just evaluates to the address of the i<sup>th</sup> element.

<sup>Slice elements act as variables, and &amp;x evaluates to the address of the x variable. Think about it: since &amp;sl[i] is the address of the ith element, the address does not need nor use the struct value, why would it make a copy?</sup>

If your slices are so big that you're worried about the performance impact of (implicit) copies, you really should consider storing pointers in the slice in the first place, and that way you can make your loop and accessing elements much simpler without having to worry about copies:

func myFunc(sl []*MyStruct) {
    for _, v := range sl {
        v.P = &quot;bar&quot;
        // other properties mutations
    }
}

Also note that if your slice holds non-pointers, and you want to change a field of a slice element, indexing the slice and referring to the field also does not involve copying the struct element:

func myFunc(sl []MyStruct) {
    for i := range sl {
        sl[i].P = &quot;bar&quot;
        // other properties mutations
    }
}

Yes, this may be more verbose and may be less efficient if you have to modify multiple fields (but compilers may also recognize and optimize the evaluation of multiple sl[i] expressions).

huangapple
  • 本文由 发表于 2022年11月16日 18:54:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/74459213.html
匿名

发表评论

匿名网友

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

确定