一个长度为零且容量为零的切片仍然可以指向底层数组并防止垃圾回收吗?

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

Can a zero-length and zero-cap slice still point to an underlying array and prevent garbage collection?

问题

让我们看下面的情况:

a := make([]int, 10000)
a = a[len(a):]

根据《Go Slices: Usage and Internals》中所述,downslicing 存在一个可能的问题。对于任何切片 a,如果你执行 a[start:end],它仍然指向原始内存,所以如果你不进行复制,一个小的 downslice 可能会长时间地保留一个非常大的数组在内存中。

然而,这个例子被选择为一个切片,它不仅应该具有零长度,还应该具有零容量。类似的问题可以针对构造 a = a[0:0:0] 提出。

当前的实现是否仍然保持对底层内存的指针引用,阻止它被垃圾回收,或者它是否意识到一个没有 lencap 的切片不可能引用任何东西,因此在下一次 GC 暂停期间回收原始的后备数组(假设没有其他引用存在)?

编辑:在 Playground 上使用 reflectunsafe 进行测试发现指针是非零的:

func main() {
    a := make([]int, 10000)
    a = a[len(a):]

    aHeader := *(*reflect.SliceHeader)((unsafe.Pointer(&a)))
    fmt.Println(aHeader.Data)

    a = make([]int, 0, 0)
    aHeader = *(*reflect.SliceHeader)((unsafe.Pointer(&a)))
    fmt.Println(aHeader.Data)
}

然而,这并不一定回答了问题,因为第二个切片从来没有包含任何内容,但它的数据字段仍然是非零的。即使如此,指针可能只是 uintptr(&a[len(a)-1]) + sizeof(int),它位于后备内存块之外,因此不会触发实际的垃圾回收,尽管这似乎不太可能,因为那样会阻止其他对象的垃圾回收。非零值也可能只是 Playground 的奇怪现象。

英文:

Let's take the following scenario:

a := make([]int, 10000)
a = a[len(a):]

As we know from "Go Slices: Usage and Internals" there's a "possible gotcha" in downslicing. For any slice a if you do a[start:end] it still points to the original memory, so if you don't copy, a small downslice could potentially keep a very large array in memory for a long time.

However, this case is chosen to result in a slice that should not only have zero length, but zero capacity. A similar question could be asked for the construct a = a[0:0:0].

Does the current implementation still maintain a pointer to the underlying memory, preventing it from being garbage collected, or does it recognize that a slice with no len or cap could not possibly reference anything, and thus garbage collect the original backing array during the next GC pause (assuming no other references exist)?

Edit: Playing with reflect and unsafe on the Playground reveals that the pointer is non-zero:

func main() {
	a := make([]int, 10000)
	a = a[len(a):]

	aHeader := *(*reflect.SliceHeader)((unsafe.Pointer(&a)))
	fmt.Println(aHeader.Data)

	a = make([]int, 0, 0)
	aHeader = *(*reflect.SliceHeader)((unsafe.Pointer(&a)))
	fmt.Println(aHeader.Data)
}

http://play.golang.org/p/L0tuzN4ULn

However, this doesn't necessarily answer the question because the second slice that NEVER had anything in it also has a non-zero pointer as the data field. Even so, the pointer could simply be uintptr(&a[len(a)-1]) + sizeof(int) which would be outside the block of backing memory and thus not trigger actual garbage collection, though this seems unlikely since that would prevent garbage collection of other things. The non-zero value could also conceivably just be Playground weirdness.

答案1

得分: 1

根据你的示例,重新切片会复制切片头部,包括指向新切片的数据指针,所以我编写了一个小测试来尝试强制运行时在可能的情况下重用内存。

我希望这个过程更具确定性,但至少在x86_64上的go1.3中,它显示原始数组使用的内存最终被重用(在这种形式下,它在playground中不起作用)。

package main

import (
	"fmt"
	"unsafe"
)

func check(i uintptr) {
	fmt.Printf("Value at %d: %d\n", i, *(*int64)(unsafe.Pointer(i)))
}

func garbage() string {
	s := ""
	for i := 0; i < 100000; i++ {
		s += "x"
	}
	return s
}

func main() {
	s := make([]int64, 100000)
	s[0] = 42

	p := uintptr(unsafe.Pointer(&s[0]))
	check(p)

	z := s[0:0:0]
	s = nil
	fmt.Println(z)
	garbage()
	check(p)
}

以上是要翻译的内容。

英文:

As seen in your example, re-slicing copies the slice header, including the data pointer to the new slice, so I put together a small test to try and force the runtime to reuse the memory if possible.

I'd like this to be more deterministic, but at least with go1.3 on x86_64, it shows that the memory used by the original array is eventually reused (it does not work in the playground in this form).

package main

import (
	&quot;fmt&quot;
	&quot;unsafe&quot;
)

func check(i uintptr) {
	fmt.Printf(&quot;Value at %d: %d\n&quot;, i, *(*int64)(unsafe.Pointer(i)))
}

func garbage() string {
	s := &quot;&quot;
	for i := 0; i &lt; 100000; i++ {
		s += &quot;x&quot;
	}
	return s
}

func main() {
	s := make([]int64, 100000)
	s[0] = 42

	p := uintptr(unsafe.Pointer(&amp;s[0]))
	check(p)

	z := s[0:0:0]
	s = nil
	fmt.Println(z)
	garbage()
	check(p)
}

huangapple
  • 本文由 发表于 2014年5月21日 23:48:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/23788085.html
匿名

发表评论

匿名网友

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

确定