SoFunction
Updated on 2025-03-01

Detailed explanation of three Golang array copy methods and performance analysis

In Go, we can useforappend()andcopy()When copying arrays, we can pay more attention to copy performance in some scenarios that are more sensitive to performance and have more array copies. This document mainly compares the performance of these three methods.

test

The test condition is to divide a 64KB byte array into 64 blocks for copying.

Test code

package test

import (
	"testing"
)

const (
	blocks    = 64
	blockSize = 1024
)

var block = make([]byte, blockSize)

func BenchmarkFori(b *) {
	a := make([]byte, blocks*blockSize)
	for n := 0; n < ; n++ {
		for i := 0; i < blocks; i++ {
			for j := 0; j < blockSize; j++ {
				a[i*blockSize+j] = block[j]
			}
		}
	}
}

func BenchmarkAppend(b *) {
	a := make([]byte, 0, blocks*blockSize)
	for n := 0; n < ; n++ {
		a = a[:0]
		for i := 0; i < blocks; i++ {
			a = append(a, block...)
		}
	}
}

func BenchmarkCopy(b *) {
	a := make([]byte, blocks*blockSize)
	for n := 0; n < ; n++ {
		for i := 0; i < blocks; i++ {
			copy(a[i*blockSize:], block)
		}
	}
}

Test results

You can see that the performance of copy is the best, of course the performance of append is also close to that of copy, and the performance of for is poor.

BenchmarkFori-8            19831             52749 ns/op
BenchmarkAppend-8         775945              1478 ns/op
BenchmarkCopy-8           815556              1473 ns/op

Principle analysis

We simply analyze the principles of copy and append.

copy

Code

You can see that it will be called in the endmemmove()The entire piece of copy memory is implemented in assembly, so the performance is the best.

// slicecopy is used to copy from a string or slice of pointerless elements into a slice.
func slicecopy(toPtr , toLen int, fromPtr , 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

Code

Append will eventually be converted into the following code during the compilation period, which is also calledmemmove()The entire piece of copy memory is actually similar to that of 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))

Summarize

Copy method performance Suitable for scenes
for Poor Scenarios where append and copy cannot be used, such as different types, more complex judgments are required, etc.
copy good Suitable for the method of allocating array capacity in advance and not being appended at the tail.
append good Suitable for most cases, additional tail

In most cases, it is recommended to use append, which not only has good performance and dynamically expand capacity, but also has a clearer code!

This is the end of this article about the detailed explanation of the three Golang array copy methods and performance analysis. For more related Golang array copy content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!