Matrizen: zwei Indizes, eine Adresse

Matrizen: zwei Indizes, eine Adresse

Eine Matrix ist ein Raster aus Zahlen, die in Zeilen und Spalten angeordnet sind. Eine Zeile ist eine horizontale Linie des Rasters, die von links nach rechts gelesen wird. Eine Spalte ist eine vertikale Linie des Rasters, die von oben nach unten gelesen wird. Der Speicher kennt keine Zeilen oder Spalten. Er ist eine lange Folge von Bytes, dieselbe Art von Vektor, die in der vorherigen Lektion behandelt wurde. Um eine Matrix zu speichern, müssen ihre Zeilen und Spalten nacheinander in dieser einzigen Folge abgelegt werden. Die übliche Wahl ist die Row-Major-Reihenfolge: jede Zahl der ersten Zeile speichern, dann jede Zahl der zweiten Zeile, und so weiter, bis jede Zeile abgelegt wurde.

Zwei Indizes, eine Adresse

Die Position eines Elements in einer Matrix wird durch zwei Indizes angegeben: den Zeilenindex, zu welcher Zeile es gehört, und den Spaltenindex, zu welcher Spalte es gehört. Die Zählung beginnt bei beiden bei 0, genauso wie die Indizes in der Vektorlektion funktionierten. Der Speicher enthält eine einzige flache Folge von Zahlen, daher müssen die beiden Indizes zuerst zu einem einzigen flachen Index kombiniert werden: den Zeilenindex mit der Anzahl der Spalten der Matrix multiplizieren, dann den Spaltenindex addieren. Von dort aus wird der flache Index auf dieselbe Weise wie der Index eines Vektors zu einer Byte-Adresse: address = base + index * 4.

Eine Zeile und eine Spalte zu einer Adresse zusammenfassen
mul t4, t1, t3 # t4 = Zeilenindex * Anzahl der Spalten
add t4, t4, t2 # t4 = das + Spaltenindex (der flache Index)
slli t4, t4, 2 # t4 = Byte-Offset = Index * 4
add t5, t0, t4 # t5 = Adresse des Elements in dieser Zeile und Spalte
lw t5, 0(t5) # t5 = Wert an dieser Adresse

multipliziert den Zeilenindex mit der Anzahl der Spalten. In der Vektorlektion konnte die Multiplikation mit einer Verschiebung durchgeführt werden, weil 4 eine Zweierpotenz ist, aber die Anzahl der Spalten einer Matrix ist normalerweise keine, daher wird hier anstelle von verwendet. Der Spaltenindex wird addiert, und das Ergebnis wird mit einem weiteren mit 4 skaliert (4 ist eine Zweierpotenz, also funktioniert eine Verschiebung für diesen Teil weiterhin). Das ergibt den Byte-Offset des Elements an der gegebenen Zeile und Spalte.

Zeilenmajor-Indizierung
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

Zeile 0, Spalte 0 entspricht Index 0.

Das Raster links zeigt, wie eine Matrix auf Papier gezeichnet wird. Die einzelne Zeile rechts zeigt, wie dieselben Zahlen im Speicher angeordnet sind. Die Hervorhebung bewegt sich in Leserichtung durch das Raster: Zeile 0 von links nach rechts, dann Zeile 1, dann Zeile 2. Im selben Moment leuchtet dasselbe Element in der flachen Zeile darunter auf, weil die Multiplikation des Zeilenindex mit der Anzahl der Spalten und die Addition des Spaltenindex die Adresse dieses Elements ergibt, was dem auf beiden Seiten angezeigten Hex-Wert entspricht. RISC-V arbeitet nur mit der flachen Zeile rechts. Das Raster ist eine Form, die das .data-Layout Ihres Programms und Ihr eigenes Lesen davon dieser einzigen Zeile auferlegen, unter Verwendung der oben gezeigten Arithmetik , und . Sobald das Raster und die flache Zeile als eine Sache verstanden werden, die zweimal gezeigt wird, deckt dasselbe Muster jedes Raster von Daten ab: die Pixelzeilen eines Bildes, ein Spielbrett oder eine Tabellenkalkulation. Jedes ist ein Array, das mit einer zusätzlichen Multiplikation erreicht wird.

Matrix addieren

Addiere jedes Element einer 3-mal-3-Matrix
.data
m: .word 1, 2, 3, 4, 5, 6, 7, 8, 9 # 3x3-Matrix, zeilenweise gespeichert
.text
la t0, m # Basisadresse
li t1, 3 # Anzahl der Zeilen
li t2, 3 # Anzahl der Spalten
li t3, 0 # aktueller Zeilenindex, beginnt bei 0
li s0, 0 # laufende Summe
row_loop:
beq t3, t1, row_done
li t4, 0 # aktueller Spaltenindex, beginnt bei 0
col_loop:
beq t4, t2, col_done
mul t5, t3, t2 # Zeilenindex * Anzahl der Spalten
add t5, t5, t4 # + Spaltenindex = abgeflachter Index
slli t5, t5, 2 # Byte-Offset = Index * 4
add t6, t0, t5 # Adresse des Elements in dieser Zeile und Spalte
lw t6, 0(t6) # Wert an dieser Adresse
add s0, s0, t6 # Summe += Element
addi t4, t4, 1 # zur nächsten Spalte weiter
j col_loop
col_done:
addi t3, t3, 1 # zur nächsten Zeile weiter
j row_loop
row_done:
mv a0, s0 # gibt die Summe aus: 1+2+...+9 = 45
li a7, 1
ecall
li a7, 10
ecall

Zwei verschachtelte Schleifen indizieren die Matrix. Die äußere Schleife wählt eine Zeile aus und die innere Schleife wählt eine Spalte innerhalb dieser Zeile aus. multipliziert den Zeilenindex mit der Anzahl der Spalten. Das Addieren des Spaltenindex und das Skalieren mit 4 ergibt die Adresse dieses Elements. Der an dieser Adresse gespeicherte Wert wird geladen und zur laufenden Summe addiert. Wenn die innere Schleife eine Zeile beendet, geht die äußere Schleife zur nächsten Zeile über, bis alle 3 Zeilen verarbeitet wurden.

ℹ
Die zeilenweise Anordnung speichert alle Zahlen der ersten Zeile, dann alle Zahlen der zweiten Zeile, und setzt dieses Muster fort. Dies ist eine Konvention und keine Regel. Andere Sprachen, darunter Fortran, verwenden die spaltenweise Anordnung: alle Zahlen der ersten Spalte, dann alle Zahlen der zweiten Spalte, und so weiter. Der Schreiber der Daten und der Leser der Daten müssen sich darüber einig sein, welches Layout verwendet wurde. RISC-V liest und schreibt an der Adresse, die Sie berechnen.