矩阵:两个索引,一个地址
矩阵是按行和列排列的数字网格。行是网格中的一条水平线,从左到右读取。列是网格中的一条垂直线,从上到下读取。内存没有行或列的概念。它是一长串字节,与上一课介绍的向量是同一种东西。要存储矩阵,它的行和列必须依次放入这一条线中。通常的选择是行优先顺序:先存储第一行的每个数字,再存储第二行的每个数字,一直继续到每一行都被放置完毕。
两个索引,一个地址
元素在矩阵中的位置由两个索引给出:行索引表示它属于哪一行,列索引表示它属于哪一列。两者的计数都从 0 开始,与向量课中索引的工作方式相同。内存保存的是单一扁平的数字序列,因此这两个索引必须先合并为一个扁平化索引:将行索引乘以矩阵的列数,再加上列索引。由此,扁平化索引会像向量的索引一样变成字节地址: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 的幂,这个乘法可以用移位完成,但矩阵的列数通常不是 2 的幂,所以这里用 而不是 。加上列索引后,结果再用另一个 乘以 4(4 是 2 的幂,所以这部分仍然可以用移位)。这样就得到了给定行和列处元素的字节偏移量。
行优先索引
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。
.data 布局和你自己的阅读方式,使用上面展示的 、 和 运算,强加在这一条线上的形状。一旦把网格和扁平化行理解为同一个事物的两次展示,同样的模式就适用于任何数据网格:图像的像素行、游戏棋盘或电子表格。每一个都是一个数组,只需额外一次乘法即可访问。矩阵求和
将 3 乘 3 矩阵的每个元素相加
.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
两个嵌套循环为矩阵建立索引。外层循环选择一行,内层循环选择该行中的一列。 将行索引乘以列数。加上列索引并乘以 4 就得到该元素的地址。加载该地址处存储的值并加到累计总和上。当内层循环完成一行后,外层循环移到下一行,直到全部 3 行都被处理完毕。
ℹ
行优先顺序先存储第一行的所有数字,然后存储第二行的所有数字,并依此模式继续。这是一种约定,而不是规则。其他语言,包括 Fortran,使用列优先顺序:先第一列的所有数字,然后第二列的所有数字,依此类推。数据的写入者和数据的读取者必须就使用了哪种布局达成一致。RISC-V 在你计算出的任何地址上进行读写。
