行列:2つのインデックス、1つのアドレス
行列とは、数値を行と列に並べたグリッドのことです。行とはグリッドの1本の水平な並びで、左から右へ読みます。列とはグリッドの1本の垂直な並びで、上から下へ読みます。メモリには行や列という概念はありません。メモリは1本の長いバイトの並びであり、前のレッスンで扱ったベクトルと同じ種類のものです。行列を格納するには、その行と列をその1本の並びの中に順番に置かなければなりません。通常選ばれるのは行優先順です。つまり、最初の行のすべての数値を格納し、次に2番目の行のすべての数値を格納し、すべての行を置き終えるまで続けます。
2つのインデックス、1つのアドレス
行列における要素の位置は2つのインデックスで与えられます。行インデックスはその要素がどの行に属するかを示し、列インデックスはどの列に属するかを示します。どちらの数え方も0から始まり、ベクトルのレッスンでインデックスが機能したのと同じです。メモリは数値の単一の平坦な並びを保持するため、2つのインデックスはまず1つの平坦化されたインデックスに結合されなければなりません。行インデックスに行列の列数を掛け、その結果に列インデックスを足します。そこから平坦化されたインデックスは、ベクトルのインデックスと同じ方法でバイトアドレスになります。address = base + index * 4。
mul t4, t1, t3 # t4 = 行インデックス * 列数add t4, t4, t2 # t4 = that + 列インデックス(平坦化されたインデックス)slli t4, t4, 2 # t4 = バイトオフセット = インデックス * 4add t5, t0, t4 # t5 = その行と列にある要素のアドレスlw t5, 0(t5) # t5 = そのアドレスの値
は行インデックスに列数を掛けます。ベクトルのレッスンでは4が2の冪であるため、この掛け算はシフトで行えましたが、行列の列数は通常そうではないため、ここでは の代わりに が使われます。列インデックスが足され、その結果はもう1つの によって4でスケーリングされます(4は2の冪なので、この部分には依然としてシフトが使えます)。これにより、指定された行と列にある要素のバイトオフセットが得られます。
行0、列0はインデックス0に対応します。
.data レイアウトとあなた自身の読み取りが、上に示した 、、 の演算を使ってその単一の行に課す形です。グリッドと平坦化された行が、2度示された1つのものとして理解されれば、同じパターンがあらゆるデータのグリッドをカバーします。画像のピクセルの行、ゲーム盤、あるいはスプレッドシートです。それぞれは1つの配列であり、1回の余分な掛け算で到達できます。行列の和
.datam: .word 1, 2, 3, 4, 5, 6, 7, 8, 9 # 3x3行列、行ごとに格納.textla t0, m # ベースアドレスli t1, 3 # 行数li t2, 3 # 列数li t3, 0 # 現在の行インデックス、0から始まるli s0, 0 # 累計row_loop:beq t3, t1, row_doneli t4, 0 # 現在の列インデックス、0から始まるcol_loop:beq t4, t2, col_donemul t5, t3, t2 # 行インデックス * 列数add t5, t5, t4 # + 列インデックス = フラット化されたインデックスslli t5, t5, 2 # バイトオフセット = インデックス * 4add t6, t0, t5 # その行と列にある要素のアドレスlw t6, 0(t6) # そのアドレスの値add s0, s0, t6 # 合計 += 要素addi t4, t4, 1 # 次の列へ進むj col_loopcol_done:addi t3, t3, 1 # 次の行へ進むj row_looprow_done:mv a0, s0 # 合計を出力: 1+2+...+9 = 45li a7, 1ecallli a7, 10ecall
2つのネストされたループが行列にインデックスを付けます。外側のループは行を選択し、内側のループはその行内の列を選択します。 は行インデックスに列数を掛けます。列インデックスを足して4でスケーリングすると、その要素のアドレスが得られます。そのアドレスに格納された値がロードされ、実行中の合計に加算されます。内側のループが1つの行を終えると、外側のループは次の行へ移動し、3つの行すべてが処理されるまで続きます。
