Matrices : deux indices, une adresse

Matrices : deux indices, une adresse

Une matrice est une grille de nombres disposés en lignes et en colonnes. Une ligne est une ligne horizontale de la grille, lue de gauche à droite. Une colonne est une ligne verticale de la grille, lue de haut en bas. La mémoire n'a aucune notion de lignes ni de colonnes. C'est une longue suite d'octets, le même genre de vecteur que celui abordé dans la leçon précédente. Pour stocker une matrice, ses lignes et ses colonnes doivent être placées l'une après l'autre dans cette unique suite. Le choix habituel est l'ordre row-major : stocker chaque nombre de la première ligne, puis chaque nombre de la deuxième ligne, et continuer jusqu'à ce que toutes les lignes aient été placées.

Deux indices, une adresse

La position d'un élément dans une matrice est donnée par deux indices : l'indice de ligne, à quelle ligne il appartient, et l'indice de colonne, à quelle colonne il appartient. Le comptage pour les deux commence à 0, de la même manière que les indices fonctionnaient dans la leçon sur les vecteurs. La mémoire contient une seule séquence plate de nombres, donc les deux indices doivent d'abord être combinés en un seul indice aplati : multiplier l'indice de ligne par le nombre de colonnes de la matrice, puis ajouter l'indice de colonne. À partir de là, l'indice aplati devient une adresse d'octet de la même manière que l'indice d'un vecteur : address = base + index * 4.

Aplatissement d'une ligne et d'une colonne en une adresse
mul t4, t1, t3 # t4 = index de ligne * nombre de colonnes
add t4, t4, t2 # t4 = cela + indice de colonne (l'indice aplati)
slli t4, t4, 2 # t4 = décalage en octets = index * 4
add t5, t0, t4 # t5 = adresse de l'élément à cette ligne et colonne
lw t5, 0(t5) # t5 = valeur à cette adresse

multiplie l'indice de ligne par le nombre de colonnes. Dans la leçon sur les vecteurs, la multiplication pouvait être effectuée avec un décalage car 4 est une puissance de deux, mais le nombre de colonnes d'une matrice ne l'est généralement pas, donc est utilisé ici au lieu de . L'indice de colonne est ajouté, et le résultat est mis à l'échelle par 4 avec un autre (4 est une puissance de deux, donc un décalage fonctionne encore pour cette partie). Cela produit le décalage en octets de l'élément à la ligne et à la colonne données.

Indexation en ligne majeure
index=0× 3 +0=0
1
0x10010000
2
0x10010004
3
0x10010008
4
0x1001000c
5
0x10010010
6
0x10010014
7
0x10010018
8
0x1001001c
9
0x10010020
01
0x10010000
12
0x10010004
23
0x10010008
34
0x1001000c
45
0x10010010
56
0x10010014
67
0x10010018
78
0x1001001c
89
0x10010020

Ligne 0, colonne 0 correspond à l'indice 0.

La grille à gauche montre comment une matrice est dessinée sur papier. La ligne unique à droite montre comment ces mêmes nombres sont disposés en mémoire. Le surlignage parcourt la grille dans l'ordre de lecture : ligne 0 de gauche à droite, puis ligne 1, puis ligne 2. Au même moment, le même élément s'illumine dans la ligne aplatie en dessous, car multiplier l'indice de ligne par le nombre de colonnes et ajouter l'indice de colonne donne l'adresse de cet élément, ce qui correspond à la valeur hexadécimale affichée des deux côtés. RISC-V ne travaille qu'avec la ligne aplatie à droite. La grille est une forme que la disposition .data de votre programme et votre propre lecture de celle-ci imposent à cette unique ligne, en utilisant l'arithmétique , et montrée ci-dessus. Une fois que la grille et la ligne aplatie sont comprises comme une seule chose montrée deux fois, le même schéma couvre n'importe quelle grille de données : les rangées de pixels d'une image, un plateau de jeu ou un tableur. Chacun est un tableau, atteint avec une multiplication supplémentaire.

Somme d'une matrice

Additionner chaque élément d'une matrice 3 par 3
.data
m: .word 1, 2, 3, 4, 5, 6, 7, 8, 9 # matrice 3x3, stockée ligne par ligne
.text
la t0, m # adresse de base
li t1, 3 # nombre de lignes
li t2, 3 # nombre de colonnes
li t3, 0 # index de ligne courant, commence à 0
li s0, 0 # total courant
row_loop:
beq t3, t1, row_done
li t4, 0 # index de colonne courant, commence à 0
col_loop:
beq t4, t2, col_done
mul t5, t3, t2 # index de ligne * nombre de colonnes
add t5, t5, t4 # + index de colonne = index aplati
slli t5, t5, 2 # décalage en octets = index * 4
add t6, t0, t5 # adresse de l'élément à cette ligne et colonne
lw t6, 0(t6) # valeur à cette adresse
add s0, s0, t6 # total += élément
addi t4, t4, 1 # passe à la colonne suivante
j col_loop
col_done:
addi t3, t3, 1 # passe à la ligne suivante
j row_loop
row_done:
mv a0, s0 # affiche le total : 1+2+...+9 = 45
li a7, 1
ecall
li a7, 10
ecall

Deux boucles imbriquées indexent la matrice. La boucle externe sélectionne une ligne et la boucle interne sélectionne une colonne dans cette ligne. multiplie l'indice de ligne par le nombre de colonnes. Ajouter l'indice de colonne et mettre à l'échelle par 4 donne l'adresse de cet élément. La valeur stockée à cette adresse est chargée et ajoutée au total cumulé. Lorsque la boucle interne termine une ligne, la boucle externe passe à la ligne suivante, jusqu'à ce que les 3 lignes aient été traitées.

ℹ
L'ordre row-major stocke tous les nombres de la première ligne, puis tous les nombres de la deuxième ligne, et continue dans ce schéma. C'est une convention plutôt qu'une règle. D'autres langages, Fortran parmi eux, utilisent l'ordre column-major : tous les nombres de la première colonne, puis tous les nombres de la deuxième colonne, et ainsi de suite. L'écrivain des données et le lecteur des données doivent être d'accord sur la disposition utilisée. RISC-V lit et écrit à l'adresse que vous calculez.