在Go语言中,切片(slices)不会分配任何内存。

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

Slices in golang do not allocate any memory?

问题

这个链接:http://research.swtch.com/godata

它说("Slices"章节的第三段):

> 由于切片是多字结构而不是指针,切片操作不需要分配内存,甚至不需要为切片头部分配内存,通常可以将其保存在堆栈上。这种表示使得切片的使用成本与在C中传递显式指针和长度对差不多。Go最初将切片表示为指向上述结构的指针,但这样做意味着每个切片操作都会分配一个新的内存对象。即使使用快速分配器,这也会给垃圾收集器带来很多不必要的工作,正如上面的字符串一样,我们发现程序更喜欢传递显式索引而不是进行切片操作。去除间接引用和分配使得切片足够廉价,以避免在大多数情况下传递显式索引。

为什么它不分配任何内存呢?如果它是一个多字结构或指针,它不需要分配内存吗?然后它提到它最初是指向那个切片结构的指针,并且需要为一个新对象分配内存。为什么现在不需要这样做了?非常困惑。

英文:

This link: http://research.swtch.com/godata

It says (third paragraph of section Slices):

> Because slices are multiword structures, not pointers, the slicing
> operation does not need to allocate memory, not even for the slice
> header, which can usually be kept on the stack. This representation
> makes slices about as cheap to use as passing around explicit pointer
> and length pairs in C. Go originally represented a slice as a pointer
> to the structure shown above, but doing so meant that every slice
> operation allocated a new memory object. Even with a fast allocator,
> that creates a lot of unnecessary work for the garbage collector, and
> we found that, as was the case with strings above, programs avoided
> slicing operations in favor of passing explicit indices. Removing the
> indirection and the allocation made slices cheap enough to avoid
> passing explicit indices in most cases.

What...? Why does it not allocate any memory? If it is a multiword structure or a pointer? Does it not need to allocate memory? Then it mentions that it was originally a pointer to that slice structure, and it needed to allocate memory for a new object. Why does it not need to do that now? Very confused

答案1

得分: 6

为了扩展Pravin Mishra的答案

“切片操作”是指诸如s1[x:y]这样的操作,而不是切片的初始化或make([]int, x)。例如:

var s1 = []int{0, 1, 2, 3, 4, 5} // << - 分配内存(或放在堆栈上)
s2 := s1[1:3]                    // << - 通常不会分配内存

也就是说,第二行代码类似于:

type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
}
…
example := SliceHeader{&s1[1], 2, 5}

通常,像example这样的局部变量会被放在堆栈上。这就好像使用结构体来代替:

var exampleData            uintptr
var exampleLen, exampleCap int

这些example*变量会被放在堆栈上。只有当代码执行return &exampleotherFunc(&example),或者以其他方式允许指向它的指针逃逸时,编译器才会被迫在堆上分配结构体(或切片头)。

然后它提到它最初是指向该切片结构的指针,并且需要为一个新对象分配内存。为什么现在不需要这样做了?

想象一下,如果你这样做:

example2 := &SliceHeader{…相同…}
// 或者
example3 := new(SliceHeader)
example3.Data = …
example3.Len = …
example3.Cap = …

也就是说,类型是*SliceHeader而不是SliceHeader。根据你提到的内容,这实际上是切片在Go 1.0之前的情况。

过去,example2example3都必须在堆上分配内存。这就是所谓的“为一个新对象分配内存”。我认为现在逃逸分析会尝试将这两个对象都放在堆栈上,只要指针在函数内部保持局部性,这就不再是一个大问题。无论如何,避免一级间接引用是好的,与重复复制指针并反复解引用相比,复制三个整数几乎总是更快的。

英文:

To expand on Pravin Mishra's answer:

> the slicing operation does not need to allocate memory.

"Slicing operation" refers to things like s1[x:y] and not slice initialization or make([]int, x). For example:

var s1 = []int{0, 1, 2, 3, 4, 5} // &lt;&lt;- allocates (or put on stack)
s2 := s1[1:3]                    // &lt;&lt;- does not (normally) allocate

That is, the second line is similar to:

type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
}
…
example := SliceHeader{&amp;s1[1], 2, 5}

Usually local variables like example get put onto the stack. It's just like if this was done instead of using a struct:

var exampleData            uintptr
var exampleLen, exampleCap int

Those example* variables go onto the stack.
Only if the code does return &amp;example or otherFunc(&amp;example) or otherwise allows a pointer to this to escape will the compiler be forced to allocate the struct (or slice header) on the heap.

> Then it mentions that it was originally a pointer to that slice structure, and it needed to allocate memory for a new object. Why does it not need to do that now?

Imagine that instead of the above you did:

example2 := &amp;SliceHeader{…same…}
// or
example3 := new(SliceHeader)
example3.Data = …
example3.Len = …
example3.Cap = …

i.e. the type is *SliceHeader rather than SliceHeader.
This is effectively what slices used to be (pre Go 1.0) according to what you mention.

It also used to be that both example2 and example3 would have to be allocated on the heap. That is the "memory for a new object" being refered to. I think that now escape analysis will try and put both of these onto the stack as long as the pointer(s) are kept local to the function so it's not as big of an issue anymore. Either way though, avoiding one level of indirection is good, it's almost always faster to copy three ints compared to copying a pointer and dereferencing it repeatedly.

答案2

得分: 2

每种数据类型在初始化时都会分配内存。在博客中,他明确提到:

切片操作不需要分配内存。

他是正确的。现在看看,在Go语言中切片是如何工作的。

切片保存对底层数组的引用,如果将一个切片赋值给另一个切片,两者都引用同一个数组。如果一个函数接受一个切片参数,并对切片元素进行更改,这些更改将对调用者可见,类似于传递指向底层数组的指针。

英文:

Every data type allocates memory when it's initialized. In blog, he clearly mention

> the slicing operation does not need to allocate memory.

And he is right. Now see, how slice works in golang.

> Slices hold references to an underlying array, and if you assign one
> slice to another, both refer to the same array. If a function takes a
> slice argument, changes it makes to the elements of the slice will be
> visible to the caller, analogous to passing a pointer to the
> underlying array.

huangapple
  • 本文由 发表于 2015年3月22日 23:54:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/29196475.html
匿名

发表评论

匿名网友

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

确定