我一直在寻找一种从 go 中的切片中删除元素的方法,我基本上遇到了两种方法,
1:
s = append(s[:i], s[i+1:]...)
2:
s[i], s = s[len(s)-1], s[:len(s)-1]
版本 2 被认为更快。
我做了一些基准测试,结果我不太明白:
1:
BenchmarkSliceOrder-12 1000000000 0.0001861 ns/op
2:
BenchmarkSliceNoOrder-12 1000000000 0.0001618 ns/op
这表明它没有真正的区别。 这里需要注意的是,要删除的元素的索引是在切片的整个长度上随机选择的 (
i := rand.Intn(limit)
)。我还发现有一些特定的指数确实会产生影响,但最多为 2。
由于我认为版本 1 在索引较小时涉及更多复制,因此我又进行了两次测试:
i = 2, limit 100000
:
BenchmarkSliceOrder-12 1000000000 0.008157 ns/op
BenchmarkSliceNoOrder-12 1000000000 0.01165 ns/op
i = limit - 2, limit 100000
:
BenchmarkSliceOrder-12 1000000000 0.01550 ns/op
BenchmarkSliceNoOrder-12 1000000000 0.01599 ns/op
那么 Go 中的切片有什么用呢?我们不应该担心优化还是什么吗?
我的参考代码:
var limit int = 100000
func BenchmarkSliceOrder(b *testing.B) {
s := make([]string, limit)
for i := 0; i < limit; i++ {
s[i] = fmt.Sprintf("string%d", i)
}
//i := rand.Intn(limit)
i := limit / 2
s = append(s[:i], s[i+1:]...)
}
func BenchmarkSliceNoOrder(b *testing.B) {
s := make([]string, limit)
for i := 0; i < limit; i++ {
s[i] = fmt.Sprintf("string%d", i)
}
//i := rand.Intn(limit)
i := limit / 2
s[i] = s[len(s)-1]
s = s[:len(s)-1]
}
正如JimB正确指出的那样,你的基准是不正确的。
尝试类似的事情
package main
import (
"testing"
)
const (
length = 1_000
remove = 500
)
func BenchmarkSliceOrder(b *testing.B) {
var a [length]int
for range b.N {
s := a[:]
s = append(s[:remove], s[remove+1:]...)
_ = s
}
}
func BenchmarkSliceNoOrder(b *testing.B) {
var a [length]int
for range b.N {
s := a[:]
s[remove] = s[len(s)-1]
s = s[:len(s)-1]
_ = s
}
}
请注意,这是一个微观基准测试,其结果取决于许多因素,例如 CPU 缓存、RAM 速度等。
我不会仅仅因为你在机器上看到一些数字就假设一种方法更快,请参见例如“如何在 Go 中编写准确的基准”: