三种Golang数组拷贝方式及性能分析详解
在Go语言中,我们可以使用for
、append()
和copy()
进行数组拷贝,对于某些对性能比较敏感且数组拷贝比较多的场景,我们可以会对拷贝性能比较关注,这篇文件主要是对比一下这三种方式的性能。
测试
测试条件是把一个64KB的字节数组分为64个块进行复制。
测试代码
package test
import (
"testing"
)
const (
blocks = 64
blockSize = 1024
)
var block = make([]byte, blockSize)
func BenchmarkFori(b *testing.B) {
a := make([]byte, blocks*blockSize)
for n := 0; n < b.N; n++ {
for i := 0; i < blocks; i++ {
for j := 0; j < blockSize; j++ {
a[i*blockSize+j] = block[j]
}
}
}
}
func BenchmarkAppend(b *testing.B) {
a := make([]byte, 0, blocks*blockSize)
for n := 0; n < b.N; n++ {
a = a[:0]
for i := 0; i < blocks; i++ {
a = append(a, block...)
}
}
}
func BenchmarkCopy(b *testing.B) {
a := make([]byte, blocks*blockSize)
for n := 0; n < b.N; n++ {
for i := 0; i < blocks; i++ {
copy(a[i*blockSize:], block)
}
}
}
测试结果
可以看到copy的性能是最好的,当然append的性能也接近copy,for性能较差。
BenchmarkFori-8 19831 52749 ns/op
BenchmarkAppend-8 775945 1478 ns/op
BenchmarkCopy-8 815556 1473 ns/op
原理分析
我们简单分析copy和append的原理。
copy
代码
可以看到最终都会调用memmove()
整块拷贝内存,而且是用汇编实现的,因此性能是最好的。
// slicecopy is used to copy from a string or slice of pointerless elements into a slice.
func slicecopy(toPtr unsafe.Pointer, toLen int, fromPtr unsafe.Pointer, fromLen int, width uintptr) int {
if fromLen == 0 || toLen == 0 {
return 0
}
n := fromLen
if toLen < n {
n = toLen
}
if width == 0 {
return n
}
size := uintptr(n) * width
if raceenabled {
callerpc := getcallerpc()
pc := funcPC(slicecopy)
racereadrangepc(fromPtr, size, callerpc, pc)
racewriterangepc(toPtr, size, callerpc, pc)
}
if msanenabled {
msanread(fromPtr, size)
msanwrite(toPtr, size)
}
if size == 1 { // common case worth about 2x to do here
// TODO: is this still worth it with new memmove impl?
*(*byte)(toPtr) = *(*byte)(fromPtr) // known to be a byte pointer
} else {
memmove(toPtr, fromPtr, size)
}
return n
}
append
代码
append最终会被编译期转换成以下代码,也是调用了memmove()
整块拷贝内存,因此其实性能是和copy差不多的。
s := l1
n := len(s) + len(l2)
// Compare as uint so growslice can panic on overflow.
if uint(n) > uint(cap(s)) {
s = growslice(s, n)
}
s = s[:n]
memmove(&s[len(l1)], &l2[0], len(l2)*sizeof(T))
总结
拷贝方式 | 性能 | 适合场景 |
---|---|---|
for | 较差 | 无法使用append和copy的场景,比如类型不同,需要更加复杂的判断等 |
copy | 好 | 适合提前已经分配数组容量,且不是尾部追加的方式 |
append | 好 | 适合大多数情况,尾部追加 |
大部分情况下还是建议使用append,不仅性能好,动态扩展容量,而且代码看起来更加清晰!
到此这篇关于三种Golang数组拷贝方式及性能分析详解的文章就介绍到这了,更多相关Golang数组拷贝内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341