Macierze: dwa indeksy, jeden adres

Macierze: dwa indeksy, jeden adres

Macierz to siatka liczb ułożonych w wiersze i kolumny. Wiersz to jedna pozioma linia siatki, czytana od lewej do prawej. Kolumna to jedna pionowa linia siatki, czytana od góry do dołu. Pamięć nie ma pojęcia wierszy ani kolumn. To jeden długi ciąg bajtów, ten sam rodzaj wektora omawiany w poprzedniej lekcji. Aby zapisać macierz, jej wiersze i kolumny muszą zostać umieszczone jeden po drugim w tym jednym ciągu. Zwykle wybiera się porządek row-major: zapisz każdą liczbę pierwszego wiersza, potem każdą liczbę drugiego wiersza, i kontynuuj, aż każdy wiersz zostanie umieszczony.

Dwa indeksy, jeden adres

Pozycja elementu w macierzy jest określona przez dwa indeksy: indeks wiersza, czyli do którego wiersza należy, oraz indeks kolumny, czyli do której kolumny należy. Liczenie obu zaczyna się od 0, tak samo jak indeksy działały w lekcji o wektorach. Pamięć przechowuje pojedynczy płaski ciąg liczb, więc oba indeksy trzeba najpierw połączyć w jeden spłaszczony indeks: pomnóż indeks wiersza przez liczbę kolumn macierzy, a następnie dodaj indeks kolumny. Stąd spłaszczony indeks staje się adresem bajtowym w taki sam sposób jak indeks wektora: address = base + index * 4.

Spłaszczanie wiersza i kolumny do adresu
mul t4, t1, t3 # t4 = indeks wiersza * liczba kolumn
add t4, t4, t2 # t4 = that + indeks kolumny (indeks spłaszczony)
slli t4, t4, 2 # t4 = przesunięcie w bajtach = indeks * 4
add t5, t0, t4 # t5 = adres elementu w tym wierszu i kolumnie
lw t5, 0(t5) # t5 = wartość pod tym adresem

mnoży indeks wiersza przez liczbę kolumn. W lekcji o wektorach mnożenie można było wykonać przesunięciem, ponieważ 4 jest potęgą dwójki, ale liczba kolumn macierzy zwykle nią nie jest, więc tutaj użyto zamiast . Dodaje się indeks kolumny, a wynik jest skalowany przez 4 za pomocą kolejnego (4 jest potęgą dwójki, więc przesunięcie nadal działa dla tej części). To daje przesunięcie bajtowe elementu w podanym wierszu i kolumnie.

Indeksowanie wierszowe
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

Wiersz 0, kolumna 0 odpowiada indeksowi 0.

Siatka po lewej pokazuje, jak macierz rysuje się na papierze. Pojedynczy wiersz po prawej pokazuje, jak te same liczby są rozmieszczone w pamięci. Podświetlenie przesuwa się po siatce w kolejności czytania: wiersz 0 od lewej do prawej, potem wiersz 1, potem wiersz 2. W tym samym momencie ten sam element zapala się w spłaszczonym wierszu poniżej, ponieważ pomnożenie indeksu wiersza przez liczbę kolumn i dodanie indeksu kolumny daje adres tego elementu, który odpowiada wartości szesnastkowej pokazanej po obu stronach. RISC-V pracuje wyłącznie ze spłaszczonym wierszem po prawej. Siatka to kształt, który układ .data twojego programu i twoje własne czytanie narzucają na ten pojedynczy wiersz, używając arytmetyki , i pokazanej powyżej. Gdy siatka i spłaszczony wiersz zostaną zrozumiane jako jedna rzecz pokazana dwa razy, ten sam wzorzec obejmuje dowolną siatkę danych: wiersze pikseli obrazu, planszę gry lub arkusz kalkulacyjny. Każda z nich to jedna tablica, do której dociera się przez jedno dodatkowe mnożenie.

Suma macierzy

Dodaj każdy element macierzy 3 na 3
.data
m: .word 1, 2, 3, 4, 5, 6, 7, 8, 9 # macierz 3x3, przechowywana wiersz po wierszu
.text
la t0, m # adres bazowy
li t1, 3 # liczba wierszy
li t2, 3 # liczba kolumn
li t3, 0 # bieżący indeks wiersza, zaczyna się od 0
li s0, 0 # suma bieżąca
row_loop:
beq t3, t1, row_done
li t4, 0 # bieżący indeks kolumny, zaczyna się od 0
col_loop:
beq t4, t2, col_done
mul t5, t3, t2 # indeks wiersza * liczba kolumn
add t5, t5, t4 # + indeks kolumny = spłaszczony indeks
slli t5, t5, 2 # przesunięcie w bajtach = indeks * 4
add t6, t0, t5 # adres elementu w tym wierszu i kolumnie
lw t6, 0(t6) # wartość pod tym adresem
add s0, s0, t6 # suma += element
addi t4, t4, 1 # przechodzi do następnej kolumny
j col_loop
col_done:
addi t3, t3, 1 # przechodzi do następnego wiersza
j row_loop
row_done:
mv a0, s0 # wypisuje sumę: 1+2+...+9 = 45
li a7, 1
ecall
li a7, 10
ecall

Dwie zagnieżdżone pętle indeksują macierz. Zewnętrzna pętla wybiera wiersz, a wewnętrzna wybiera kolumnę w tym wierszu. mnoży indeks wiersza przez liczbę kolumn. Dodanie indeksu kolumny i skalowanie przez 4 daje adres tego elementu. Wartość zapisana pod tym adresem jest wczytywana i dodawana do bieżącej sumy. Gdy wewnętrzna pętla kończy wiersz, zewnętrzna pętla przechodzi do następnego wiersza, aż wszystkie 3 wiersze zostaną przetworzone.

ℹ
Wierszowy porządek przechowuje wszystkie liczby pierwszego wiersza, potem wszystkie liczby drugiego wiersza, i kontynuuje w tym wzorze. To jest konwencja, a nie zasada. Inne języki, w tym Fortran, używają kolumnowego porządku: wszystkie liczby pierwszej kolumny, potem wszystkie liczby drugiej kolumny, i tak dalej. Autor danych i czytelnik danych muszą się zgodzić, która układ została użyta. RISC-V odczytuje i zapisuje pod dowolnym adresem, który obliczysz.