In Go, we can usefor
、append()
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!