Mówiąc ogólnie, algorytmy działające szybciej na GPU to takie, w których wykonuje się ten sam typ instrukcji w wielu różnych punktach danych.
Łatwym tego przykładem jest mnożenie macierzy.
Załóżmy, że wykonujemy obliczenia macierzowe
A × B = C
Prosty algorytm procesora może wyglądać podobnie
// zaczynając od C = 0
for (int i = 0; i < C_Width; i++)
{
for (int j = 0; j < C_Height; j++)
{
for (int k = 0; k < A_Width; k++)
{
for (int l = 0; l < B_Height; l++)
{
C[j, i] += A[j, k] * B[l, i];
}
}
}
}
Kluczową rzeczą do zobaczenia tutaj jest to, że istnieje wiele zagnieżdżonych pętli i każdy krok musi być wykonywany jeden po drugim.
Zobacz schemat tego
Zauważ, że obliczenie każdego elementu C nie zależy od żadnego z pozostałych elementów. Dlatego nie ma znaczenia, w jakiej kolejności są wykonywane obliczenia.
Na GPU operacje te można wykonywać jednocześnie.
Jądro GPU do obliczania mnożenia macierzy wyglądałoby mniej więcej tak
__kernel void Multiply
(
__global float * A,
__global float * B,
__global float * C
)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
for (int k = 0; k < A_Width; k++)
{
for (int l = 0; l < B_Height; l++)
{
C[x, y] += A[x, k] * B[l, y];
}
}
}
To jądro ma tylko dwa wewnętrzne pętle for. Program wysyłający to zadanie do GPU nakazuje GPU wykonanie tego jądra dla każdego punktu danych w C. GPU wykona każdą z tych instrukcji jednocześnie na wielu wątkach. Podobnie jak w starym powiedzeniu „Tańsze o tuzin” procesory graficzne są zaprojektowane tak, aby szybciej robić to samo wiele razy.
Istnieją jednak pewne algorytmy, które spowalniają procesor graficzny. Niektóre nie są dobrze dostosowane do GPU.
Jeśli na przykład istniały zależności danych, tj .: wyobraź sobie, że obliczenia każdego elementu C zależą od poprzednich elementów. Programista musiałby umieścić barierę w jądrze, aby czekać na zakończenie każdego poprzedniego obliczenia. To byłoby poważne spowolnienie.
Ponadto algorytmy, które mają dużo logiki rozgałęziania, tj .:
__kernel Foo()
{
if (somecondition)
{
do something
}
else
{
do something completely different
}
}
zwykle działają wolniej na GPU, ponieważ GPU nie robi już tego samego w każdym wątku.
Jest to uproszczone wyjaśnienie, ponieważ należy wziąć pod uwagę wiele innych czynników. Na przykład wysyłanie danych między CPU a GPU jest również czasochłonne. Czasami warto wykonać obliczenia na GPU, nawet jeśli jest on szybszy na CPU, aby uniknąć dodatkowego czasu wysyłania (i odwrotnie).
Również wiele współczesnych procesorów obsługuje teraz współbieżność z wielordzeniowymi procesorami hiperwątkowymi.
Procesory graficzne również nie wydają się zbyt dobre do rekurencji, zobacz tutaj, co prawdopodobnie wyjaśnia niektóre problemy z algorytmem QR. Uważam, że ktoś ma pewne rekurencyjne zależności danych.