Векторы: индексирование по позиции

Векторы: индексирование по позиции

Вектор — это массив: последовательность чисел, хранящихся рядом друг с другом в памяти. В предыдущем уроке вы суммировали его, перемещая указатель вперёд на 4 байта для каждого элемента. Тот же метод работает и здесь. Есть ещё один распространённый способ добраться до элемента: по индексу. Индекс — это позиция элемента, отсчитываемая от начала, где первый элемент имеет индекс 0, второй имеет индекс 1, и так далее.

Суммирование вектора с продвижением указателя

Суммируйте вектор, продвигая указатель
.data
a: .word 5, 10, 15, 20 # четыре элемента
.text
la t0, a # t0 = адрес a
li t1, 4 # t1 = количество оставшихся элементов
li t2, 0 # t2 = текущая сумма
loop:
beq t1, zero, done
lw t3, 0(t0) # загружает текущий элемент
add t2, t2, t3 # прибавляет к сумме
addi t0, t0, 4 # переходит к следующему слову (4 байта)
addi t1, t1, -1 # на один меньше
j loop
done:
mv a0, t2 # печатает итог: 5+10+15+20 = 50
li a7, 1
ecall
li a7, 10
ecall

Цикл выполняется следующим образом. t0 хранит адрес a, а t1 хранит количество оставшихся элементов, начиная с 4. На каждом проходе завершает цикл, когда счётчик достигает нуля. загружает элемент по адресу в t0. добавляет это значение к текущей сумме. продвигает указатель к следующему слову. уменьшает счётчик. После четырёх проходов (5 + 10 + 15 + 20) done выводит 50. Это тот же шаблон, использованный в уроке о памяти, теперь применённый к вектору.

От индекса к адресу

Индексация вычисляет адрес из базового указателя и позиции: address = base + index * 4. Число 4 — это размер слова в байтах. Компилятор генерирует такое же вычисление для a[i] в таком языке, как C. Сам индекс никогда не обращается к памяти. Это делает только вычисленный из него адрес.

Индекс в адрес, используя сдвиг
slli t5, t3, 2 # t5 = индекс * 4 (размер слова)
add t6, t0, t5 # t6 = адрес element[index]
lw t6, 0(t6) # t6 = element[index]

сдвигает индекс в t3 влево на 2 бита, что умножает его на 4 (два удвоения: 2 x 2) — размер одного слова. Добавление результата к базовому указателю даёт адрес element[index]. Этот сдвиг — тот же метод степеней двойки из урока арифметики, и это стандартный способ масштабирования индекса на размер слова.

Вычисление адреса
address=base+0×4=base+0
a[0]3
0x10010000
a[1]6
0x10010004
a[2]9
0x10010008
a[3]12
0x1001000c

a[0]: индекс 0, смещение 0.

Полоса указателя и шестнадцатеричный значок движутся вместе, пока индекс проходит через 0, 1, 2, 3. Они представляют один и тот же факт в двух формах: i как позиция и адрес, который эта позиция порождает после того, как масштабирует её на 4 и добавляет к базе. Шестнадцатеричные числа — это фактические адреса, которые занимает a, когда симулятор запускает эту программу. Цель — предсказать следующий адрес до того, как он появится. Это позволяет читать внутри инкрементирующего цикла или a[i] в C-подобном языке и представлять адрес памяти, к которому он обращается, вместо того чтобы воспринимать запись как чёрный ящик.

Суммирование вектора по индексу

Суммируйте вектор, используя индекс
.data
a: .word 5, 10, 15, 20 # четыре элемента
.text
la t0, a # t0 = базовый адрес a
li t1, 4 # t1 = количество элементов
li t2, 0 # t2 = индекс, начинается с 0
li t3, 0 # t3 = текущая сумма
loop:
beq t2, t1, done
slli t4, t2, 2 # t4 = индекс * 4 (смещение в байтах)
add t5, t0, t4 # t5 = адрес a[index]
lw t5, 0(t5) # t5 = a[index]
add t3, t3, t5 # total += a[index]
addi t2, t2, 1 # переходит к следующему индексу
j loop
done:
mv a0, t3 # печатает итог: 5+10+15+20 = 50
li a7, 1
ecall
li a7, 10
ecall

Цикл выполняется следующим образом. t2 — это индекс, начиная с 0. На каждом проходе завершает цикл, когда индекс достигает 4 — количества элементов, хранящегося в t1. преобразует индекс в байтовое смещение. добавляет это смещение к базовому адресу в t0, чтобы получить адрес a[index]. загружает значение, хранящееся там. Это значение добавляется к текущей сумме в t3, а переходит к следующему индексу. После четырёх проходов (5 + 10 + 15 + 20) done выводит 50.

Сложение двух векторов, элемент за элементом

c[i] = a[i] + b[i]
.data
a: .word 3, 6, 9, 12 # первый вектор
b: .word 1, 2, 3, 4 # второй вектор
c: .space 16 # место для 4 слов результата
.text
la t0, a # базовый адрес a
la t1, b # базовый адрес b
la t2, c # базовый адрес c
li t3, 0 # индекс, начинается с 0
li t4, 4 # количество элементов
li s0, 0 # текущая сумма
loop:
beq t3, t4, done
slli t5, t3, 2 # смещение в байтах = индекс * 4
add t6, t0, t5 # адрес a[index]
lw t6, 0(t6) # значение a[index]
add a0, t1, t5 # адрес b[index]
lw a0, 0(a0) # значение b[index]
add t6, t6, a0 # a[index] + b[index]
add a0, t2, t5 # адрес c[index]
sw t6, 0(a0) # c[index] = a[index] + b[index]
add s0, s0, t6 # итог += c[index]
addi t3, t3, 1 # переходит к следующему индексу
j loop
done:
mv a0, s0 # печатает итог: 4+8+12+16 = 40
li a7, 1
ecall
li a7, 10
ecall

Цикл выполняется следующим образом. преобразует индекс t3 в байтовое смещение. Добавление его к каждому базовому указателю (t0, t1, t2) даёт адреса a[i], b[i] и c[i]. На каждом проходе оба элемента загружаются, складываются, сохраняются в c и добавляются к текущей сумме в s0. переходит к следующему индексу, и цикл повторяется, пока все четыре элемента не будут обработаны.

💡
Этот шаблон индексации, база + индекс * размер элемента, — это то, как компилируется любое обращение к массиву, в любом языке. Держите это в уме: следующий урок использует ту же идею дважды одновременно, чтобы добраться до матрицы.