Pour accélérer l'opération d'ajout de tranche, nous devons allouer suffisamment de capacité. Il y a deux façons d'ajouter une tranche, voici le code:
func BenchmarkSliceAppend(b *testing.B) {
a := make([]int, 0, b.N)
for i := 0; i < b.N; i++ {
a = append(a, i)
}
}
func BenchmarkSliceSet(b *testing.B) {
a := make([]int, b.N)
for i := 0; i < b.N; i++ {
a[i] = i
}
}
Et le résultat est:
BenchmarkSliceAppend-4 200000000 7,87 ns/op 8 B/op 0 allocs/op
BenchmarkSliceSet-4 300000000 5,76 ns/op 8 B/op
a[i] = i
Est plus rapide que a = append(a, i)
et je veux savoir pourquoi?
a[i] = i
Affecte simplement la valeur i
à a[i]
. Ce n'est pas ajouté, c'est juste un simple affectation .
Maintenant, ajoutez:
a = append(a, i)
En théorie, ce qui suit se produit:
Cela appelle la fonction intégrée append()
. Pour cela, il doit d'abord copier la tranche a
(en-tête de tranche, le tableau de support ne fait pas partie de l'en-tête), et il doit créer une tranche temporaire pour le paramètre variadic qui contiendra la valeur i
.
Ensuite, il doit redimensionner a
s'il a suffisamment de capacité (il a dans votre cas) comme a = a[:len(a)+1]
- ce qui implique d'assigner la nouvelle tranche à a
à l'intérieur de la fonction append()
.
(Si a
n'aurait pas la capacité suffisante pour faire l'ajout "sur place", un nouveau tableau devrait être alloué, le contenu de la tranche copié, puis l'assignation/l'ajout serait exécuté - mais ce n'est pas le cas ici.)
Affecte ensuite i
à a[len(a)-1]
.
Retourne ensuite la nouvelle tranche de append()
, et cette nouvelle tranche est affectée à la variable locale a
.
Beaucoup de choses se produisent ici par rapport à une simple affectation. Même si bon nombre de ces étapes sont optimisées et/ou intégrées, comme ajout minimum à l'attribution de i
à un élément de la tranche, la variable locale a
du type de tranche (qui est un en-tête de tranche) doit être mis à jour à chaque cycle de la boucle .
Lecture recommandée: The Go Blog: Tableaux, tranches (et chaînes): La mécanique de 'append'
Il semble que certaines améliorations du compilateur Go ou du runtime aient été introduites depuis la publication de cette question, alors maintenant (Go 1.10.1
) il n'y a pas de différence significative entre append
et l'affectation directe par index.
De plus, j'ai dû modifier légèrement vos repères en raison des paniques du MOO.
package main
import "testing"
var result []int
const size = 32
const iterations = 100 * 1000 * 1000
func doAssign() {
data := make([]int, size)
for i := 0; i < size; i++ {
data[i] = i
}
result = data
}
func doAppend() {
data := make([]int, 0, size)
for i := 0; i < size; i++ {
data = append(data, i)
}
result = data
}
func BenchmarkAssign(b *testing.B) {
b.N = iterations
for i := 0; i < b.N; i++ {
doAssign()
}
}
func BenchmarkAppend(b *testing.B) {
b.N = iterations
for i := 0; i < b.N; i++ {
doAppend()
}
}
Résultats:
➜ bench_slice_assign go test -bench=Bench .
goos: linux
goarch: AMD64
BenchmarkAssign-4 100000000 80.9 ns/op
BenchmarkAppend-4 100000000 81.9 ns/op
PASS
ok _/home/isaev/troubles/bench_slice_assign 16.288s