脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - 三种Golang数组拷贝方式及性能分析详解

三种Golang数组拷贝方式及性能分析详解

2022-08-24 13:56jiaxwu Golang

在Go语言中,我们可以使用for、append()和copy()进行数组拷贝。这篇文章主要为大家详细介绍一下这三种方式的具体实现与性能分析,需要的可以参考一下

在Go语言中,我们可以使用forappend()copy()进行数组拷贝,对于某些对性能比较敏感且数组拷贝比较多的场景,我们可以会对拷贝性能比较关注,这篇文件主要是对比一下这三种方式的性能。

测试

测试条件是把一个64KB的字节数组分为64个块进行复制。

测试代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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()整块拷贝内存,而且是用汇编实现的,因此性能是最好的。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 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差不多的。

?
1
2
3
4
5
6
7
8
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数组拷贝内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://juejin.cn/post/7134957581801357348

延伸 · 阅读

精彩推荐