ベクトル:位置によるインデックス付け

ベクトル:位置によるインデックス付け

ベクトルとは配列のことです。メモリ上で隣り合って格納された数値の並びです。前のレッスンでは、各要素ごとにポインタを4バイト進めながら合計を求めました。同じ方法がここでも使えます。要素に到達するもう一つの一般的な方法があります。それはインデックスによる方法です。インデックスとは、先頭から数えた要素の位置であり、最初の要素のインデックスは0、2番目は1、というように続きます。

ポインタを進めてベクトルを合計する

ポインタを進めてベクトルを合計する
.data
a: .word 5, 10, 15, 20 # 4要素
.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 # 残り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のアドレスにある要素をロードします。はその値を累計に加算します。はポインタを次のワードに進めます。はカウントを減らします。4回のパスの後(5 + 10 + 15 + 20)、doneは50を出力します。これはメモリのレッスンで使われたのと同じパターンであり、今度はベクトルに適用されています。

インデックスからアドレスへ

インデックス計算は、ベースポインタと位置からアドレスを計算します。address = base + index * 4。この4はワードのサイズ(バイト単位)です。コンパイラは、Cなどの言語におけるa[i]に対して、これと同じ計算を生成します。インデックス自体はメモリにアクセスしません。それから計算されたアドレスのみがアクセスします。

シフトを使用してアドレスをインデックス化する
slli t5, t3, 2 # t5 = インデックス * 4 (ワードサイズ)
add t6, t0, t5 # t6 = element[index]のアドレス
lw t6, 0(t6) # t6 = element[index]

はt3のインデックスを2ビット左にシフトし、それを4倍します(2回の倍化:2 x 2)。これは1ワードのサイズです。その結果をベースポインタに加算すると、element[index]のアドレスが得られます。このシフトは算術のレッスンで扱った2の累乗の方法と同じであり、インデックスをワードのサイズでスケーリングする標準的な方法です。

アドレス計算
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。

ポインタバーと16進数バッジは、インデックスが0、1、2、3と循環するにつれて一緒に動きます。それらは同じ事実を2つの形で示します。iは位置として、そしてその位置がによって4倍にスケーリングされベースに加算された後に生成するアドレスです。16進数の数値は、シミュレータがこのプログラムを実行するときにaが占める実際のアドレスです。目的は、次のアドレスが現れる前にそれを予測することです。そうすれば、インクリメントするループ内のや、Cライクな言語のa[i]を読み、表記をブラックボックスとして扱うのではなく、それが触れるメモリアドレスを思い描くことができます。

インデックスでベクトルを合計する

インデックスを使用してベクトルを合計する
.data
a: .word 5, 10, 15, 20 # 4要素
.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から始まります。各パスで、はインデックスがt1に保持された要素数である4に達したときにループを終了します。はインデックスをバイトオフセットに変換します。はそのオフセットをt0のベースアドレスに加算して、a[index]のアドレスを生成します。はそこに格納された値をロードします。その値はt3の累計に加算され、は次のインデックスに進みます。4回のパスの後(5 + 10 + 15 + 20)、doneは50を出力します。

2つのベクトルを要素ごとに加算する

c[i] = a[i] + b[i]
.data
a: .word 3, 6, 9, 12 # 1つ目のベクトル
b: .word 1, 2, 3, 4 # 2つ目のベクトル
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の累計に加算されます。は次のインデックスに進み、4つの要素すべてが処理されるまでループが繰り返されます。

💡
このインデックスパターン、base + index * element sizeは、あらゆる言語で、すべての配列アクセスがコンパイルされる方法です。覚えておいてください。次のレッスンでは、同じ考えを一度に2回使って行列にアクセスします。