【发布时间】:2018-11-05 14:56:11
【问题描述】:
我刚刚开始使用 Go,我遇到了一种情况,我需要创建一组实体,其大小/长度仅在运行时才知道。我最初认为使用列表会很合适,但很快意识到切片是 Go 中惯用的数据结构。 好奇,我写了以下基准
package main
import (
"container/list"
"testing"
)
var N = 10000000
func BenchmarkSlices(B *testing.B) {
s := make([]int, 1)
for i := 0; i < N; i += 1 {
s = append(s, i)
}
}
func BenchmarkLists(B *testing.B) {
l := list.New()
for i := 0; i < N; i += 1 {
l.PushBack(i)
}
}
这给了我
BenchmarkSlices-4 2000000000 0.03 ns/op
BenchmarkLists-4 1 1665489308 ns/op
鉴于append 会创建一个新数组,并在旧数组已满时将所有数据从旧数组复制到新数组,因此在上面的示例中,我预计列表的性能优于切片。但是,我的期望显然是错误的,我正在尝试了解原因。
为了更好地理解append 在需要时如何创建新数组,我编写了以下内容:
package main
import "fmt"
func describe(s []int) {
fmt.Printf("len = %d, cap = %d\n", len(s), cap(s))
}
func main() {
s := make([]int, 2)
for i := 0; i < 15; i += 1 {
fmt.Println(i)
describe(s)
s = append(s, i)
}
}
这给了我
0
len = 2, cap = 2
1
len = 3, cap = 4
2
len = 4, cap = 4
3
len = 5, cap = 8
4
len = 6, cap = 8
5
len = 7, cap = 8
6
len = 8, cap = 8
7
len = 9, cap = 16
8
len = 10, cap = 16
9
len = 11, cap = 16
10
len = 12, cap = 16
11
len = 13, cap = 16
12
len = 14, cap = 16
13
len = 15, cap = 16
14
len = 16, cap = 16
目前我唯一的猜测是,为什么切片比列表性能更好的是,为大小为两倍的新数组分配内存并复制所有数据比每次插入时为单个元素分配内存要快。 我的猜测正确吗?我错过了什么吗?
【问题讨论】:
-
您谈论
append创建新数组--append仅适用于切片,不适用于数组。我不太确定你在这里问什么。 -
container/list提供双向链表。如果您需要双向链表,请使用它。如果您不需要双向链表,请不要使用它。不应考虑性能。 -
@Flimzy 谢谢你的留言。据我所知,引擎盖下没有数组就不可能切片。因此,如果我理解正确,
append将创建一个数组和一个切片并返回后者。它是否正确?我没有深入细节,因为我认为这与我实际提出的问题无关。感谢您指出这个细节,但我的措辞确实可能会误导某些人。 -
@Flimzy 这也是我一开始的立场。然而,通过阅读一些代码和文章,我感觉到人们倾向于使用切片,如果他们可以用切片解决问题,即使列表更适合。这让我有点困惑,这就是为什么我开始怀疑性能是否是人们倾向于尽可能多地使用切片的原因之一。如果列表在数据结构方面有意义并省略切片,您认为我应该使用列表吗,尽管它们(显然?)是惯用的?
-
我使用切片而不是双向链表,因为我几乎不需要双向链表。尽管在我大学期间讨论链表和双向链表的时间过多,但在我的编程生涯中,我只需要一次双向链表。