为什么奇数切片容量与偶数切片容量的行为不同?

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

Why does slice capacity with odd numbers differ from behavior with even numbers

问题

我注意到,当切片的容量是奇数时,切片的容量表现出不同的方式。具体来说:当向切片添加元素时,如果原始容量是偶数,切片的容量会加倍。但是,当原始容量是奇数时,容量会增加一倍然后再加一。例如:

s := make([]int, 28, 28)
s = append(s, 1) 
fmt.Println("len=", len(s), " cap=", cap(s)) // len = len + 1, cap = 2 * cap


pri := make([]int, 27, 27)
pri = append(pri, 1)
fmt.Println("len=", len(pri), " cap=", cap(pri)) // len = len + 1, cap = 2 * (cap + 1)

假设这不是一个 bug,这种行为的原因是什么?

Playground 链接:http://play.golang.org/p/wfmdobgCUF

英文:

I noticed that the capacity of slices behaves in a different way, when the capacity is an odd number. More specifically: When an element is added to a slice, the capacity of the slice is doubled when the original capacity was an even number. But when the original capacity was an odd number, the capacity is incremented by one and then doubled. Example:

s := make([]int, 28, 28)
s = append(s, 1) 
fmt.Println("len=", len(s), " cap=", cap(s)) // len = len + 1, cap = 2 * cap


pri := make([]int, 27, 27)
pri = append(pri, 1)
fmt.Println("len=", len(pri), " cap=", cap(pri)) // len = len + 1, cap = 2 * (cap + 1)	

Assuming this is not a bug, what's the reason for this behavior?

Link to playground: http://play.golang.org/p/wfmdobgCUF

答案1

得分: 20

简短回答:
它将切片容量舍入到填充分配的内存块。

长回答:
让我们来看一下Go1.5.1的源代码:https://github.com/golang/go/blob/f2e4c8b5fb3660d793b2c545ef207153db0a34b1/src/cmd/compile/internal/gc/walk.go#L2895 告诉我们append(l1, l2...)被展开为:

s := l1
if n := len(l1) + len(l2) - cap(s); n > 0 {
    s = growslice_n(s, n)
}
s = s[:len(l1)+len(l2)]
memmove(&s[len(l1)], &l2[0], len(l2)*sizeof(T))

我们感兴趣的部分,growslice_n在这里定义:https://github.com/golang/go/blob/f2e4c8b5fb3660d793b2c545ef207153db0a34b1/src/runtime/slice.go#L36

深入一点,我们找到了这个:

newcap := old.cap
if newcap+newcap < cap {
	newcap = cap
} else {
	for {
		if old.len < 1024 {
			newcap += newcap
		} else {
			newcap += newcap / 4
		}
		if newcap >= cap {
			break
		}
	}
}

/* [...] */

capmem := roundupsize(uintptr(newcap) * uintptr(et.size))
newcap = int(capmem / uintptr(et.size))

roundupsize在这里定义:https://github.com/golang/go/blob/f2e4c8b5fb3660d793b2c545ef207153db0a34b1/src/runtime/msize.go#L178

// 返回mallocgc将分配的内存块的大小。
func roundupsize(size uintptr) uintptr {
	if size < _MaxSmallSize {
		if size <= 1024-8 {
			return uintptr(class_to_size[size_to_class8[(size+7)>>3]])
		} else {
			return uintptr(class_to_size[size_to_class128[(size-1024+127)>>7]])
		}
	}
	if size+_PageSize < size {
		return size
	}
	return round(size, _PageSize)
}

它是在这里引入的:https://groups.google.com/forum/#!topic/golang-codereviews/bFGtI4Cpb_M

在增长切片时,考虑分配的内存块的大小。

英文:

Short answer

It is rounding up the slice capacity to fill the allocated memory blocks.

Long answer

Let's have a look into the Go1.5.1 source code :

https://github.com/golang/go/blob/f2e4c8b5fb3660d793b2c545ef207153db0a34b1/src/cmd/compile/internal/gc/walk.go#L2895 tells us that append(l1, l2...) is expanded to

s := l1
if n := len(l1) + len(l2) - cap(s); n &gt; 0 {
    s = growslice_n(s, n)
}
s = s[:len(l1)+len(l2)]
memmove(&amp;s[len(l1)], &amp;l2[0], len(l2)*sizeof(T))

The part we are interested in, growslice_n, is defined there : https://github.com/golang/go/blob/f2e4c8b5fb3660d793b2c545ef207153db0a34b1/src/runtime/slice.go#L36

Going a bit deeper, we find this :

newcap := old.cap
if newcap+newcap &lt; cap {
	newcap = cap
} else {
	for {
		if old.len &lt; 1024 {
			newcap += newcap
		} else {
			newcap += newcap / 4
		}
		if newcap &gt;= cap {
			break
		}
	}
}

/* [...] */

capmem := roundupsize(uintptr(newcap) * uintptr(et.size))
newcap = int(capmem / uintptr(et.size))

roundupsize is defined there : https://github.com/golang/go/blob/f2e4c8b5fb3660d793b2c545ef207153db0a34b1/src/runtime/msize.go#L178

// Returns size of the memory block that mallocgc will allocate if you ask for the size.
func roundupsize(size uintptr) uintptr {
	if size &lt; _MaxSmallSize {
		if size &lt;= 1024-8 {
			return uintptr(class_to_size[size_to_class8[(size+7)&gt;&gt;3]])
		} else {
			return uintptr(class_to_size[size_to_class128[(size-1024+127)&gt;&gt;7]])
		}
	}
	if size+_PageSize &lt; size {
		return size
	}
	return round(size, _PageSize)
}

And it was introduced there : https://groups.google.com/forum/#!topic/golang-codereviews/bFGtI4Cpb_M

> When growing slice take into account size of the allocated memory block.

huangapple
  • 本文由 发表于 2015年10月7日 22:56:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/32995623.html
匿名

发表评论

匿名网友

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

确定