我花了一些时间尝试使用Go的内部结构,最后我使用切片编写了自己的stack实现。 正如this帖子中的reddit用户正确指出并且this SO answer中的其他用户所述,Go已尝试优化切片调整大小。
但事实证明,我宁愿使用我自己的切片增长实现而不是坚持使用默认切片来提高性能。
这是我用来保存堆栈的结构:
type Stack struct {
slice []interface{}
blockSize int
}
const s_DefaultAllocBlockSize = 20;
这是我自己的<{1}}方法的实现
Push
这是一个简单的实现
func (s *Stack) Push(elem interface{}) {
if len(s.slice) + 1 == cap(s.slice) {
slice := make([]interface{}, 0, len(s.slice) + s.blockSize)
copy(slice, s.slice)
s.slice = slice
}
s.slice = append(s.slice, elem)
}
运行基准测试我已经使用Go的testing软件包实现了我自己的实现方式:
func (s *Stack) Push(elem interface{}) {
s.slice = append(s.slice, elem)
}
依赖普通Benchmark_PushDefaultStack 20000000 87.7 ns/op 24 B/op 1 allocs/op
,结果如下
append
我运行测试的机器是2011年初的Mac Book Pro,2.3 GHz Intel Core i5,配备8GB RAM 1333MHz DDR3
修改 实际问题是:我的实现是否真的比默认的追加行为更快?或者我没有考虑到某些事情?
答案 0 :(得分:3)
我相信你的例子更快,因为你有一个相当小的数据集并且分配初始容量为0.在你的追加版本中,你通过提前更大幅度地增加块大小来抢占大量的分配(20 )绕过(在这种情况下)昂贵的reallocs,带你通过所有那些小的容量0,1,2,4,8,16,32,64等
如果您的数据集大得多,则可能会被大型副本的成本边缘化。我在Go看到了很多滥用切片的行为。通过使用合理的默认容量切片,可以获得明显的性能提升。
答案 1 :(得分:3)
阅读您的代码,测试,基准测试和结果,很容易发现它们存在缺陷。完整的代码审查超出了StackOverflow的范围。
一个特定的错误。
// Push pushes a new element to the stack
func (s *Stack) Push(elem interface{}) {
if len(s.slice)+1 == cap(s.slice) {
slice := make([]interface{}, 0, len(s.slice)+s.blockSize)
copy(slice, s.slice)
s.slice = slice
}
s.slice = append(s.slice, elem)
}
应该是
// Push pushes a new element to the stack
func (s *Stack) Push(elem interface{}) {
if len(s.slice)+1 == cap(s.slice) {
slice := make([]interface{}, len(s.slice), len(s.slice)+s.blockSize)
copy(slice, s.slice)
s.slice = slice
}
s.slice = append(s.slice, elem)
}
函数
copy
将切片元素从源src
复制到 目标dst
并返回复制的元素数。该 复制的元素数量是len(src)
和len(dst)
的最小值。
您复制了0
,您应该已复制len(s.slice)
。
正如所料,您的Push算法速度非常慢:
append:
Benchmark_PushDefaultStack-4 2000000 941 ns/op 49 B/op 1 allocs/op
alediaferia:
Benchmark_PushDefaultStack-4 100000 1246315 ns/op 42355 B/op 1 allocs/op
append
的工作原理:append complexity。
还有其他错误。您的基准测试结果通常无效。