In der 4x4-Matrizenmultiplikation berechnet sich jedes Element der Ergebnis-Matrix durch das Skalarprodukt der entsprechenden Zeile der ersten Matrix matrix_a
mit der jeweiligen Spalte der zweiten Matrix matrix_B
. Dies bedeutet, dass für jedes Element der Ergebnis-Matrix die Summe der Produkte der passenden Elemente der Zeile und Spalte gebildet wird:
c(ij)
= a(i1)
x b(1j)
+ a(i2)
x b(2j)
+ a(i3)
x b(3j)
+ a(i4)
x b(4j)
Da der gegebene Code die SIMD-Instruktionen von NEON verwendet, müssen die Berechnungen für mehrere Elemente parallel durchgeführt werden. Um dies zu ermöglichen, wird die Matrix matrix_B
zunächst transponiert. Dies erlaubt es, die Spalten von matrix_B
als Zeilen zu behandeln, was die parallele Verarbeitung durch SIMD erleichtert.
.data
matrix_b_tr: .word 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
Im Datenabschnitt wird der Speicher für die transponierte Version von matrix_B
reserviert. Diese transponierte Matrix matrix_b_tr
wird später verwendet, um die Spalten von matrix_B
als Zeilen zu verarbeiten.
.text
Matrix4x4_mul: @ r0 = ptr auf matrix_c, r1 = ptr auf matrix_a, r2 = ptr auf matrix_B
push {lr}
push {r4-r6}
vpush {d8-d15}
Zu Beginn sichert der Code das Link-Register (lr
), allgemeine Register (r4-r6
) und NEON-Register (d8-d15
).
vld1.32 {q8}, [r1]!
vld1.32 {q9}, [r1]!
vld1.32 {q10}, [r1]!
vld1.32 {q11}, [r1]!
Die vier Zeilen von matrix_a
werden in die NEON-Register q8
bis q11
geladen. Dies bedeutet, dass q8
die erste Zeile, q9
die zweite Zeile und so weiter enthält. Durch den Suffix !
wird der Zeiger r1
nach jeder Ladung um die entsprechende Größe der Daten (eine Zeile) inkrementiert.
ldr r1, =matrix_b_tr
push {r0}
mov r0, r2
bl matr4_transp
mov r2, r0
pop {r0}
mov r3, r0
Hier wird die Adresse von matrix_b_tr
in r1
geladen. Danach wird matrix_B
transponiert, indem die Funktion matr4_transp
aufgerufen wird. Diese Transponierung ist notwendig, um die Spalten von matrix_B
in eine Form zu bringen, die parallel mit den Zeilen von matrix_a
multipliziert werden kann. Die transponierte Matrix wird in matrix_b_tr
gespeichert, und r2
wird auf die Adresse der transponierten Matrix gesetzt.
vld1.32 {q12}, [r2]!
vld1.32 {q13}, [r2]!
vld1.32 {q14}, [r2]!
vld1.32 {q15}, [r2]
Die transponierte Matrix matrix_b_tr
wird in die Register q12
bis q15
geladen. Diese Register repräsentieren nun die Spalten von matrix_B
als Zeilen, was für die weitere SIMD-Verarbeitung notwendig ist.
@ zeile 1 x spalte 1
vmul.i32 q0, q8, q12
@ zeile 1 x spalte 2
vmul.i32 q1, q8, q13
@ zeile 1 x spalte 3
vmul.i32 q2, q8, q14
@ zeile 1 x spalte 4
vmul.i32 q3, q8, q15
Die erste Zeile von matrix_a
(in q8
) wird nun mit allen transponierten Zeilen von matrix_B
multipliziert, was den einzelnen Spalten von matrix_B
entspricht. Jede dieser Multiplikationen speichert das Ergebnis in einem NEON-Register (q0
bis q3
). Nach diesen Operationen enthält jedes dieser Register die Produkte der ersten Zeile von matrix_a
mit den jeweiligen Spalten von matrix_B
.
vtrn.32 q0, q1
vtrn.32 q2, q3
vmov d8, d1
vmov d9, d4
vswp d8, d9
vmov d1, d8
vmov d4, d9
vmov d10, d3
vmov d11, d6
vswp d10, d11
vmov d3, d10
vmov d6, d11
Nach der Multiplikation befinden sich die Produkte in einer Anordnung, die nicht direkt parallel addiert werden kann, um die Elemente der Ergebnis-Matrix zu berechnen. Daher werden die Register mit den Produkten transponiert, damit die Elemente der Lanes korrekt für die anschließende parallele Addition vorbereitet sind.
vadd.i32 q4, q0, q1
vadd.i32 q5, q2, q3
vadd.i32 q6, q4, q5
vst1.32 {q6}, [r3]!
Die Produkte in den transponierten Registern werden nun parallel aufaddiert, um die endgültigen Werte der ersten Zeile der Ergebnis-Matrix matrix_c
zu erhalten. Die Summen werden im Register q6
gespeichert und anschließend in den Speicher für die erste Zeile von matrix_c
geschrieben.
Dieser gesamte Prozess (Multiplikation, Transponierung, Addition) wird nun für die restlichen Zeilen von matrix_a
wiederholt:
@ zeile 2 x spalte 1
vmul.i32 q0, q9, q12
@ zeile 2 x spalte 2
vmul.i32 q1, q9, q13
@ zeile 2 x spalte 3
vmul.i32 q2, q9, q14
@ zeile 2 x spalte 4
vmul.i32 q3, q9, q15
Für die zweite Zeile von matrix_a
(gespeichert in q9
) wird ebenfalls jede Spalte von matrix_B
(entsprechend den transponierten Zeilen in q12
bis q15
) multipliziert. Die Ergebnisse werden auf ähnliche Weise wie bei der ersten Zeile transponiert, addiert und gespeichert.
@ transponiere produktmatrix
vtrn.32 q0, q1
vtrn.32 q2, q3
vmov d8, d1
vmov d9, d4
vswp d8, d9
vmov d1, d8
vmov d4, d9
vmov d10, d3
vmov d11, d6
vswp d10, d11
vmov d3, d10
vmov d6, d11
vadd.i32 q4, q0, q1
vadd.i32 q5, q2, q3
vadd.i32 q6, q4, q5
vst1.32 {q6}, [r3]!
Dieser Ablauf wiederholt sich für die dritte und vierte Zeile von matrix_a
:
@ zeile 3 x spalte 1
vmul.i32 q0, q10, q12
@ zeile 3 x spalte 2
vmul.i32 q1, q10, q13
@ zeile 3 x spalte 3
vmul.i32 q2, q10, q14
@ zeile 3 x spalte 4
vmul.i32 q3, q10, q15
@ transponiere produktmatrix
vtrn.32 q0, q1
vtrn.32 q2, q3
vmov d8, d1
vmov d9, d4
vswp d8, d9
vmov d1, d8
vmov d4, d9
vmov d10, d3
vmov d11, d6
vswp d10, d11
vmov d3, d10
vmov d6, d11
vadd.i32 q4, q0, q1
vadd.i32 q5, q2, q3
vadd.i32 q6, q4, q5
vst1.32 {q6}, [r3]!
@ zeile 4 x spalte 1
vmul.i32 q0, q11, q12
@ zeile 4 x spalte 2
vmul.i32 q1, q11, q13
@ zeile 4 x spalte 3
vmul.i32 q2, q11, q14
@ zeile 4 x spalte 4
vmul.i32 q3, q11, q15
@ transponiere produktmatrix
vtrn.32 q0, q1
vtrn.32 q2, q3
vmov d8, d1
vmov d9, d4
vswp d8, d9
vmov d1, d8
vmov d4, d9
vmov d10, d3
vmov d11, d6
vswp d10, d11
vmov d3, d10
vmov d6, d11
vadd.i32 q4, q0, q1
vadd.i32 q5, q2, q3
vadd.i32 q6, q4, q5
vst1.32 {q6}, [r3]
vpop {d8-d15}
pop {r4-r6}
pop {pc}
Am Ende werden alle gesicherten Register wiederhergestellt, und der Prozessor kehrt zu der aufrufenden Funktion zurück.
matr4_transp: @ Transponiere 4x4 Matrix r0 = Matrix_in r1=Matrix_out
push {lr}
push {r0}
vld1.32 {q0}, [r0]!
vld1.32 {q1}, [r0]!
vld1.32 {q2}, [r0]!
vld1.32 {q3}, [r0]!
vtrn.32 q0, q1
vtrn.32 q2, q3
vmov d8, d1
vmov d9, d4
vswp d8, d9
vmov d1, d8
vmov d4, d9
vmov d10, d3
vmov d11, d6
vswp d10, d11
vmov d3, d10
vmov d6, d11
pop {r0}
mov r0, r1
vst1.32 {q0}, [r1]!
vst1.32 {q1}, [r1]!
vst1.32 {q2}, [r1]!
vst1.32 {q3}, [r1]
pop {pc}
Die Funktion matr4_transp
lädt die vier Zeilen der Eingabematrix matrix_B
, transponiert sie, sodass die Spalten als Zeilen vorliegen, und speichert das Ergebnis in matrix_b_tr
.
zurück | Hauptmenü | weiter |