首頁 > 軟體

三種Golang陣列拷貝方式及效能分析詳解

2022-08-24 14:01:52

在Go語言中,我們可以使用forappend()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陣列拷貝內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com