Матриці: два індекси, одна адреса

Матриці: два індекси, одна адреса

Матриця — це сітка чисел, розташованих у рядках і стовпцях. Рядок — це одна горизонтальна лінія сітки, яку читають зліва направо. Стовпець — це одна вертикальна лінія сітки, яку читають згори донизу. Пам'ять не має поняття рядків чи стовпців. Це одна довга лінія байтів, той самий вид вектора, що розглядався в попередньому уроці. Щоб зберегти матрицю, її рядки та стовпці мають бути розміщені один за одним у цій єдиній лінії. Звичайний вибір — порядок row-major: зберегти кожне число першого рядка, потім кожне число другого рядка, і продовжувати, доки кожен рядок не буде розміщено.

Два індекси, одна адреса

Позиція елемента в матриці задається двома індексами: індексом рядка, до якого він належить, та індексом стовпця, до якого він належить. Відлік обох починається з 0, так само як індекси працювали в уроці про вектори. Пам'ять зберігає одну плоску послідовність чисел, тому обидва індекси спершу потрібно об'єднати в один сплощений індекс: помножте індекс рядка на кількість стовпців матриці, потім додайте індекс стовпця. Звідти сплощений індекс стає байтовою адресою так само, як індекс вектора: address = base + index * 4.

Сплющення рядка та стовпця в адресу
mul t4, t1, t3 # t4 = індекс рядка * кількість стовпців
add t4, t4, t2 # t4 = that + індекс стовпця (плаский індекс)
slli t4, t4, 2 # t4 = зсув у байтах = індекс * 4
add t5, t0, t4 # t5 = адреса елемента в цьому рядку та стовпці
lw t5, 0(t5) # t5 = значення за цією адресою

множить індекс рядка на кількість стовпців. В уроці про вектори множення можна було виконати зсувом, бо 4 — степінь двійки, але кількість стовпців матриці зазвичай нею не є, тож тут використовується замість . Індекс стовпця додається, а результат масштабується на 4 за допомогою ще одного (4 — степінь двійки, тож зсув для цієї частини все ще працює). Це дає байтове зміщення елемента в заданому рядку та стовпці.

Індексація за рядками
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

Рядок 0, стовпець 0 відповідає індексу 0.

Сітка ліворуч показує, як матриця малюється на папері. Єдиний рядок праворуч показує, як ті самі числа розташовані в пам'яті. Підсвічування рухається по сітці в порядку читання: рядок 0 зліва направо, потім рядок 1, потім рядок 2. У той самий момент той самий елемент підсвічується в сплощеному рядку нижче, бо множення індексу рядка на кількість стовпців і додавання індексу стовпця дає адресу цього елемента, яка збігається з шістнадцятковим значенням, показаним з обох боків. RISC-V працює лише зі сплощеною лінією праворуч. Сітка — це форма, яку на цю єдину лінію накладають .data-розкладка вашої програми та ваше власне її читання, використовуючи арифметику , і , показану вище. Щойно сітка та сплощений рядок розуміються як одна річ, показана двічі, той самий шаблон охоплює будь-яку сітку даних: рядки пікселів зображення, ігрове поле або електронну таблицю. Кожна з них — один масив, до якого доступаються з одним додатковим множенням.

Підсумовування матриці

Додайте всі елементи матриці 3 на 3
.data
m: .word 1, 2, 3, 4, 5, 6, 7, 8, 9 # матриця 3x3, збережена рядок за рядком
.text
la t0, m # базова адреса
li t1, 3 # кількість рядків
li t2, 3 # кількість стовпців
li t3, 0 # поточний індекс рядка, починається з 0
li s0, 0 # накопичувальна сума
row_loop:
beq t3, t1, row_done
li t4, 0 # поточний індекс стовпця, починається з 0
col_loop:
beq t4, t2, col_done
mul t5, t3, t2 # індекс рядка * кількість стовпців
add t5, t5, t4 # + індекс стовпця = сплющений індекс
slli t5, t5, 2 # зсув у байтах = індекс * 4
add t6, t0, t5 # адреса елемента в цьому рядку та стовпці
lw t6, 0(t6) # значення за цією адресою
add s0, s0, t6 # total += елемент
addi t4, t4, 1 # перейти до наступного стовпця
j col_loop
col_done:
addi t3, t3, 1 # перейти до наступного рядка
j row_loop
row_done:
mv a0, s0 # вивести total: 1+2+...+9 = 45
li a7, 1
ecall
li a7, 10
ecall

Два вкладені цикли індексують матрицю. Зовнішній цикл вибирає рядок, а внутрішній вибирає стовпець у цьому рядку. множить індекс рядка на кількість стовпців. Додавання індексу стовпця та масштабування на 4 дає адресу цього елемента. Значення, збережене за цією адресою, завантажується й додається до поточної суми. Коли внутрішній цикл завершує рядок, зовнішній цикл переходить до наступного рядка, доки всі 3 рядки не буде оброблено.

ℹ
Порядок рядків зберігає всі числа першого рядка, потім всі числа другого рядка, і продовжує у цьому шаблоні. Це конвенція, а не правило. Інші мови, включаючи Fortran, використовують порядок стовпців: всі числа першого стовпця, потім всі числа другого стовпця, і так далі. Автор даних і читач даних повинні погодитися, який макет був використаний. RISC-V читає і записує за будь-якою адресою, яку ви обчислите.