Rozróżnianie elementów wektora
QPU operuje na wektorach elementów. Mogą to być liczby zmiennoprzecinkowe albo całkowite
(pomińmy na razie pakowanie w rejestrach)
Każdy rejestr przechowuje 16-elementowe wektory.
Kiedy dane wejściowe są przetwarzane przez operacje SIMD, przetwarzany jest 16-elementowy blok.
Może okazać się konieczne rozróżnienie poszczególnych elementów wektora.
Tę funkcje osiąga się przy pomocy rejestru elem_num.
Ładowanie wartości z tego specjalnego rejestru powoduje załadowanie kolejnych liczb do elementów:
mov r0, elem_num
# wynik w akumulatorze r0
# r0=[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
Jak selektywnie wykonać operację tylko na częsci wektora, np. na 8 elementach:
mov r1, 0.0 # załadowanie 0.0 do wszystkich elementów wektora w akumulatorze r1
and.setf -, elem_num, 0x8 # ustawienie flag zgodnie z wynikiem operacji AND z numerem elementu
mov.ifnz r1, 1.0 # wybiórcze załadowanie 1.0 tylko do 8 najstarszych elementów
# wynik w akumulatorze r1
# elem: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# r1=[ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ]
Można zaobserwować, że flagi Z (zero flags) są skasowane tylko dla 8 ostatnich elementów, ponieważ
w formacie binarnym ich numer ma 1-kę w tym samym miejscu co maska (8 = 1000b).
Przez manipulację maską można wybrać inne elementy, np. mask=1 pozwala rozróżnićdifferentiates
elementy parzyste i nieparzyste, a mask=12 wybierze 4 najstarsze elementy.
Optymalizacja kodu
Ogólne zasady optymalizacji dla kodu QPU można przedstawic następująco:
- należy przygotowac jeden program dla wszystkich QPU (który zmieści się w pamięci cache),
- należy unikać szeregowych zależności danych,
- należy unikać zależności pomiędzy rdzeniami QPU,
- należy unikać konfliktów argumentów i wyników w instrukcjach,
- należy właściwie rozmieścić zmienne w rejestrach zestawu A i B,
- należy często używać akumulatorów w obliczeniach,
- należy używać obu torów ALU gdiekolwiek jest to możliwe,
- należy efektywnie wykorzystywać SIMD i wektorową organizację danych,
- należy używać pakowanych typów danych w celu oszczędności rejestrów (dla całkowitych danych),
- należy wykorzystywać instrukcje po skoku, które zostaną wpisane do potoku
W celu efektywnego użycia dwudrożnego ALU, dobrze jest zacząć od prostego programu, który wykorzytuje
tylko jedną ścieżkę ALU (nie jest wspólbieżne). Gdy taki program dobrze się przetestuje i będzie działał właściwie,
można spróbować 'podciągać' instrukcje do góry i łaczyć je z poprzednimi.
Należy to robić, aż pojawi się zależność od poprzedniego wyniku.
Można też próbować zmieniać kolejność wykonywania lub przeplatac instrukcje.
Przydatne jest także użycie akumulatorów, które dostarczają wynik już dla nastepnej instrukcji,
podczas gdy rejestry z zestawów nie.
Linki
[1] Broadcom VideoCore IV 3D, Architecture Reference Guide - dokumentacja producenta VideoCore
[2] Addendum to the Broadcom VideoCore IV documentation, Marcel Muller
[3] VideoCoreIV 3D, QPU Instructions by Marcel Muller
[4]
Hello_fft - przyklad obliczeń FFT na rdzeniach QPU.
To jest również w obrazie Raspbiana w /opt/vc/src/hello_pi/hello_fft.
[5] Dyskretna Transformata Falkowa (DWT) - przykład obliczeń w wykorzystaniem rdzeni QPU.
[6] Obliczanie SHA256 z wykorzystaniem rdzeni QPU.