Dlaczego porównania są tak drogie na GPU?

10

Próbując poprawić wydajność mojej klasy wykrywania kolizji, odkryłem, że ~ 80% czasu spędzonego na GPU spędza na warunkach, jeśli tylko próbuję ustalić granice wiader, przez które powinna się zapętlać.

Dokładniej:

  1. każdy wątek otrzymuje identyfikator, przez ten identyfikator pobiera swój trójkąt z pamięci (3 liczby całkowite), a przez te 3 pobiera swoje wierzchołki (3 liczby zmiennoprzecinkowe każdy).

  2. Następnie przekształca wierzchołki w całkowite punkty siatki (obecnie 8 x 8 x 8) i przekształca je w granice trójkąta na tej siatce

  3. Aby przekształcić 3 punkty w granice, znajduje min / max każdego wymiaru wśród każdego z punktów

Ponieważ w używanym przeze mnie języku programowania brakuje minmax, sam go stworzyłem, wygląda to tak:

procedure MinMax(a, b, c):
   local min, max

   if a > b:
      max = a
      min = b
   else:
      max = b
      min = a
   if c > max:
      max = c
   else:
      if c < min:
         min = c

   return (min, max)

Przeciętnie powinno to być 2,5 * 3 * 3 = 22,5 porównań, co ostatecznie pochłania znacznie więcej czasu niż rzeczywiste testy przecięcia krawędzi trójkąta (około 100 * 11-50 instrukcji).

W rzeczywistości stwierdziłem, że wstępne obliczenie wymaganych segmentów na jednostce centralnej (jednowątkowa, bez wektoryzacji), układanie ich w widoku GPU wraz z definicją segmentu i zmuszanie GPU do wykonania ~ 4 dodatkowych odczytów na wątek było 6 razy szybsze niż próba na miejscu ustalić granice. (zauważ, że są one ponownie obliczane przed każdym wykonaniem, ponieważ mam do czynienia z dynamicznymi siatkami)

Dlaczego więc porównanie jest tak strasznie wolne na GPU?

użytkownik29075
źródło
2
Twoje pytanie dotyczy wydajności na poziomie instrukcji określonego fragmentu kodu na określonym typie sprzętu. Dla mnie to brzmi bardziej jak pytanie programistyczne niż informatyczne.
David Richerby
7
Moje przypuszczenie jest to, że nie jest to porównanie , które są drogie, ale gałęzie. Jeśli kompilator nie korzysta z predykcji (lub GPU tego nie zapewnia), użyte zostaną gałęzie, które powodują rozwidlenie „wątku” (ponieważ GPU są zorientowane na SIMD). Konwersja warunku na maskę i użycie maski do syntezy ruchów warunkowych / zamian może być rozsądną alternatywą.
Paul A. Clayton
1
@DavidRicherby Nie jestem jednak pewien, czy jest to tak specyficzne. Czy to pytanie nie dotyczyłoby żadnej architektury SIMD?
kasperd
1
@DavidRicherby: powodem, dla którego uczymy comp comp w działach CS, jest to, że comp arch ma wpływ na wybrane algorytmy. Architektury SIMD mogą generować wysoką przepustowość tylko wtedy, gdy możesz dowiedzieć się, jak napisać program bez zagnieżdżonych gałęzi.
Wandering Logic
2
Jak stwierdza odpowiedź Wandering Logic w mniej oczywisty sposób, układy GPU działają, zakładając, że wiele „wątków” jest w tej samej instrukcji jednocześnie. Z grubsza mówiąc, procesory graficzne zajmują każdą gałąź zamiast tylko prawdziwych gałęzi. Właśnie dlatego GPU wykorzystują fakt, że sąsiedzi zwykle mają te same gałęzie; a wydajność jest okropna, gdy nie jest to prawdą.
Rob

Odpowiedzi:

10

GPU to architektury SIMD. W architekturach SIMD każda instrukcja musi być wykonana dla każdego przetwarzanego elementu. (Jest wyjątek od tej reguły, ale rzadko pomaga).

Zatem w twojej MinMaxprocedurze nie tylko każde wywołanie musi pobrać wszystkie trzy instrukcje rozgałęzienia (nawet jeśli średnio oceniane są tylko 2,5), ale każda instrukcja przypisania również zajmuje cykl (nawet jeśli tak naprawdę nie zostanie „wykonana” ).

Ten problem jest czasem nazywany rozbieżnością wątków . Jeśli twoje urządzenie ma coś w rodzaju 32 linii wykonawczych SIMD, nadal będzie miało tylko jedną jednostkę pobierania. (Tutaj termin „wątek” zasadniczo oznacza „ścieżkę wykonania SIMD”.) Zatem wewnętrznie każda ścieżka wykonania SIMD ma bit „Jestem włączony / wyłączony”, a gałęzie w rzeczywistości po prostu manipulują tym bitem. (Wyjątkiem jest to, że w momencie, w którym każda linia SIMD zostaje wyłączona, jednostka pobierania ogólnie przeskoczy bezpośrednio do klauzuli „else”).

W twoim kodzie każda linia wykonania SIMD wykonuje:

compare (a > b)
assign (max = a if a>b)
assign (min = b if a>b)
assign (max = b if not(a>b))
assign (min = a if not(a>b))
compare (c > max)
assign (max = c if c>max)
compare (c < min if not(c>max))
assign (min = c if not(c>max) and c<min)

Może się zdarzyć, że na niektórych GPU konwersja warunkowa na predykację jest wolniejsza, jeśli GPU robi to sama. Jak zauważył @ PaulA.Clayton, jeśli twój język programowania i architektura ma predykcyjną operację warunkowego przenoszenia (szczególnie jedną z postaci if (c) x = y else x = z), możesz być w stanie zrobić to lepiej. (Ale prawdopodobnie nie wiele lepiej).

Również umieszczanie c < minwarunku wewnątrz elsenie c > maxjest konieczne. Na pewno nic ci nie oszczędza, a (biorąc pod uwagę, że GPU musi automatycznie przekonwertować go na predykcję) może być bolesne, że może być zagnieżdżone w dwóch różnych warunkowych.

Wędrująca logika
źródło
2
(Przepraszam, jeśli jakakolwiek część tego jest niejasna, próbuję uzyskać odpowiedź, zanim teoretycy zamkną pytanie jako nie na temat).
Wandering Logic
Aby uzyskać więcej informacji na temat podstaw: http.developer.nvidia.com/GPUGems2/gpugems2_chapter34.html, a także nowsze obejścia: eecis.udel.edu/~cavazos/cisc879/papers/a3-han.pdf
Fizz
Jest na ten temat w tym sensie, że niektórych algorytmów nie można przyspieszyć przez równoległość SIMD. (tj .: praca, zakres itp., aby uzyskać bardziej teoretyczne wyjaśnienie, dlaczego)
Rob
1
Oto kolejny wykład na temat podstaw dywergencji people.maths.ox.ac.uk/gilesm/cuda/lecs/lec3-2x2.pdf Zwróć uwagę na to, że problem (w każdym razie na Nvidii) dotyczy tylko osnowy. Kod działający na różnych warpach może się z powodzeniem różnić. I inny artykuł proponujący metodę jego uniknięcia: hal.inria.fr/file/index/docid/649650/filename/sbiswi.pdf
Fizz
Na nieco innym kursie, ale zgodnie z komentarzami, które napisałem pod pytaniem eprint.iacr.org/2012/137.pdf, warto przeczytać: 10-krotne spowolnienie w porównaniu z przewidywaną wydajnością może być „normalne” dla GPU, chyba że spadniesz do jego montażu (zwykle przy pomocy oficjalnie nieobsługiwanych narzędzi). Możliwe, że kompilatory kierujące na GPU stały się lepsze, ale nie wstrzymałbym oddechu.
Fizz