Il existe deux façons d'utiliser les tranches pour créer une matrice. Examinons les différences entre elles.
Première méthode :
matrix := make([][]int, n)
for i := 0; i < n; i++ {
matrix[i] = make([]int, m)
}
Deuxième méthode :
matrix := make([][]int, n)
rows := make([]int, n*m)
for i := 0; i < n; i++ {
matrix[i] = rows[i*m : (i+1)*m]
}
En ce qui concerne la première méthode, faire des successions make
Les appels ne garantissent pas que vous vous retrouverez avec une matrice contiguë, vous pouvez donc avoir la matrice divisée en mémoire. Prenons un exemple avec deux routines Go qui pourraient causer cela :
- La routine #0 exécute
make([][]int, n)
pour obtenir la mémoire allouée pour matrix
en récupérant un morceau de mémoire de 0x000 à 0x07F.
- Ensuite, il démarre la boucle et fait la première ligne
make([]int, m)
en passant de 0x080 à 0x0FF.
- Dans la deuxième itération, il est préempté par l'ordonnanceur.
- Le planificateur donne le processeur à la routine #1 et celle-ci commence à fonctionner. Celle-ci utilise également
make
(pour ses propres besoins) et va de 0x100 à 0x17F (juste à côté de la première ligne de la routine #0).
- Après un certain temps, elle est préemptée et la routine 0 recommence à fonctionner.
- Il fait le
make([]int, m)
correspondant à la deuxième itération de la boucle et obtient de 0x180 à 0x1FF pour la deuxième ligne. À ce stade, nous avons déjà deux rangées divisées.
Avec la deuxième méthode, la routine fait make([]int, n*m)
pour obtenir toute la matrice allouée dans une seule tranche, en assurant la contiguïté. Après cela, une boucle est nécessaire pour mettre à jour les pointeurs de matrice vers les sous tranches correspondant à chaque ligne.
Vous pouvez jouer avec le code montré ci-dessus dans la section Go Playground pour voir la différence dans la mémoire assignée en utilisant les deux méthodes. Notez que j'ai utilisé runtime.Gosched()
uniquement dans le but de céder le processeur et de forcer le planificateur à passer à une autre routine.
Lequel utiliser ? Imaginez le pire des cas avec la première méthode, c'est-à-dire que chaque ligne n'est pas voisine en mémoire d'une autre ligne. Dans ce cas, si votre programme itère à travers les éléments de la matrice (pour les lire ou les écrire), il y aura probablement plus de manques dans le cache (donc une latence plus élevée) par rapport à la deuxième méthode en raison de la plus mauvaise localisation des données. D'autre part, avec la deuxième méthode, il peut être impossible d'obtenir un seul morceau de mémoire alloué à la matrice, en raison de la fragmentation de la mémoire (morceaux répartis dans toute la mémoire), même si, en théorie, il y a suffisamment de mémoire libre pour cela.
Par conséquent, à moins qu'il n'y ait beaucoup de fragmentation de la mémoire et que la matrice à allouer soit assez énorme, vous voudrez toujours utiliser la deuxième méthode pour profiter de la localité des données.