假设我正在创建一个切片,我事先知道我想通过对1e5
的连续调用通过一个append
元素的for循环来填充:
// Append 1e5 strings to the slice
for i := 0; i<= 1e5; i++ {
value := fmt.Sprintf("Entry: %d", i)
myslice = append(myslice, value)
}
这是初始化切片的更有效方式,原因为何:
a。声明零个字符串切片?
var myslice []string
b。预先将其长度设置为1e5
?
myslice = make([]string, 1e5)
c。是否将其长度和容量都设置为1e5
?
myslice = make([]string, 1e5, 1e5)
答案 0 :(得分:7)
您的b
和c
解决方案是相同的:使用make()
创建一个未指定容量的切片,“缺失”容量默认为给定长度。
还请注意,如果您事先创建了一个具有一定长度的切片,则无法使用append()
来填充切片,因为它将向切片中添加 new 元素,并且不会“重用”分配的元素。因此,在这种情况下,您必须使用index expression为元素分配值,例如myslice[i] = value
。
如果从容量为0的切片开始,则每当您添加不适合容量的元素时,都必须分配一个新的后备阵列,并且必须复制“旧”内容,因此解决方案的速度必须较慢本质上。
我将定义并考虑以下不同的解决方案(我使用[]int
分片来避免fmt.Sprintf()
干预/干扰我们的基准测试)
var s []int
func BenchmarkA(b *testing.B) {
for i := 0; i < b.N; i++ {
s = nil
for j := 0; j < 1e5; j++ {
s = append(s, j)
}
}
}
func BenchmarkB(b *testing.B) {
for i := 0; i < b.N; i++ {
s = make([]int, 0, 1e5)
for j := 0; j < 1e5; j++ {
s = append(s, j)
}
}
}
func BenchmarkBLocal(b *testing.B) {
for i := 0; i < b.N; i++ {
s := make([]int, 0, 1e5)
for j := 0; j < 1e5; j++ {
s = append(s, j)
}
}
}
func BenchmarkD(b *testing.B) {
for i := 0; i < b.N; i++ {
s = make([]int, 1e5)
for j := range s {
s[j] = j
}
}
}
注意:我在基准测试中使用程序包级别的变量(BLocal
除外),因为在使用局部切片变量时可能会(并且确实会)发生一些优化)。
基准测试结果:
BenchmarkA-4 1000 1081599 ns/op 4654332 B/op 30 allocs/op
BenchmarkB-4 3000 371096 ns/op 802816 B/op 1 allocs/op
BenchmarkBLocal-4 10000 172427 ns/op 802816 B/op 1 allocs/op
BenchmarkD-4 10000 167305 ns/op 802816 B/op 1 allocs/op
A
:如您所见,从nil
切片开始是最慢的,使用最多的内存和分配空间。
B
:使用容量(但长度仍为0)预分配切片并使用append:它只需要一次分配,并且速度更快,几乎是三次快速。
BLocal
:请注意,当使用局部片而不是程序包变量时,(编译器)进行了优化,并且获得了更快的速度:速度是{{1}的两倍} 。
D
:即使在使用非局部变量的情况下,也不使用D
,而是将元素分配给预分配的切片会获胜。
答案 1 :(得分:0)
在这种情况下,由于您已经知道要分配给切片的字符串元素的数量,
我更愿意使用 b或c 。
因为您将无法使用这两种方法来调整切片的大小。
如果您选择使用方法 a ,则在len等于容量之后,每次添加新元素时,切片的大小都会增加一倍。