Matrizes: dois índices, um endereço

Matrizes: dois índices, um endereço

Uma matriz é uma grade de números organizados em linhas e colunas. Uma linha é uma linha horizontal da grade, lida da esquerda para a direita. Uma coluna é uma linha vertical da grade, lida de cima para baixo. A memória não tem conceito de linhas ou colunas. É uma longa sequência de bytes, o mesmo tipo de vetor abordado na lição anterior. Para armazenar uma matriz, suas linhas e colunas devem ser colocadas uma após a outra nessa única sequência. A escolha usual é a ordem row-major: armazenar cada número da primeira linha, depois cada número da segunda linha, e continuar até que todas as linhas tenham sido colocadas.

Dois índices, um endereço

A posição de um elemento em uma matriz é dada por dois índices: o índice de linha, a qual linha ele pertence, e o índice de coluna, a qual coluna ele pertence. A contagem para ambos começa em 0, da mesma forma que os índices funcionavam na lição sobre vetores. A memória contém uma única sequência plana de números, então os dois índices devem primeiro ser combinados em um único índice achatado: multiplique o índice de linha pelo número de colunas da matriz, depois adicione o índice de coluna. A partir daí, o índice achatado se torna um endereço de byte da mesma forma que o índice de um vetor: address = base + index * 4.

Simplificando uma linha e uma coluna em um endereço
mul t4, t1, t3 # t4 = índice da linha * número de colunas
add t4, t4, t2 # t4 = isso + índice da coluna (o índice achatado)
slli t4, t4, 2 # t4 = deslocamento em bytes = índice * 4
add t5, t0, t4 # t5 = endereço do elemento nessa linha e coluna
lw t5, 0(t5) # t5 = valor nesse endereço

multiplica o índice de linha pelo número de colunas. Na lição sobre vetores, a multiplicação podia ser feita com um deslocamento porque 4 é uma potência de dois, mas o número de colunas de uma matriz geralmente não é, então é usado aqui em vez de . O índice de coluna é adicionado, e o resultado é escalado por 4 com outro (4 é uma potência de dois, então um deslocamento ainda funciona para essa parte). Isso produz o deslocamento em bytes do elemento na linha e coluna dadas.

Indexação por linha principal
índice=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

Linha 0, coluna 0 corresponde ao índice 0.

A grade à esquerda mostra como uma matriz é desenhada no papel. A única linha à direita mostra como esses mesmos números são dispostos na memória. O destaque percorre a grade na ordem de leitura: linha 0 da esquerda para a direita, depois linha 1, depois linha 2. No mesmo momento, o mesmo elemento se acende na linha achatada abaixo, porque multiplicar o índice de linha pelo número de colunas e adicionar o índice de coluna dá o endereço desse elemento, que corresponde ao valor hexadecimal mostrado em ambos os lados. O RISC-V trabalha apenas com a linha achatada à direita. A grade é uma forma que o layout .data do seu programa e sua própria leitura dele impõem a essa única linha, usando a aritmética , e mostrada acima. Uma vez que a grade e a linha achatada são entendidas como uma única coisa mostrada duas vezes, o mesmo padrão cobre qualquer grade de dados: as linhas de pixels de uma imagem, um tabuleiro de jogo ou uma planilha. Cada um é um array, alcançado com uma multiplicação extra.

Soma de uma matriz

Somar cada elemento de uma matriz 3 por 3
.data
m: .word 1, 2, 3, 4, 5, 6, 7, 8, 9 # matriz 3x3, armazenada linha por linha
.text
la t0, m # endereço base
li t1, 3 # número de linhas
li t2, 3 # número de colunas
li t3, 0 # índice da linha atual, começa em 0
li s0, 0 # total corrente
row_loop:
beq t3, t1, row_done
li t4, 0 # índice da coluna atual, começa em 0
col_loop:
beq t4, t2, col_done
mul t5, t3, t2 # índice da linha * número de colunas
add t5, t5, t4 # + índice da coluna = índice achatado
slli t5, t5, 2 # deslocamento em bytes = índice * 4
add t6, t0, t5 # endereço do elemento nessa linha e coluna
lw t6, 0(t6) # valor nesse endereço
add s0, s0, t6 # total += elemento
addi t4, t4, 1 # avança para a próxima coluna
j col_loop
col_done:
addi t3, t3, 1 # avança para a próxima linha
j row_loop
row_done:
mv a0, s0 # imprime o total: 1+2+...+9 = 45
li a7, 1
ecall
li a7, 10
ecall

Dois loops aninhados indexam a matriz. O loop externo seleciona uma linha e o loop interno seleciona uma coluna dentro dessa linha. multiplica o índice de linha pelo número de colunas. Adicionar o índice de coluna e escalar por 4 dá o endereço desse elemento. O valor armazenado nesse endereço é carregado e adicionado ao total acumulado. Quando o loop interno termina uma linha, o loop externo passa para a próxima linha, até que todas as 3 linhas tenham sido processadas.

ℹ
ordem em linha armazena todos os números da primeira linha, depois todos os números da segunda linha, e continua nesse padrão. Isso é uma convenção, e não uma regra. Outras linguagens, entre elas o Fortran, usam ordem em coluna: todos os números da primeira coluna, depois todos os números da segunda coluna, e assim por diante. O escritor dos dados e o leitor dos dados devem concordar sobre qual layout foi usado. RISC-V lê e grava no endereço que você calcular.