Programowanie współbieżne, Języki programowania, Aplikacje, Gry

CPU vs GPU: GFLOPS

Styczeń 22nd, 2010 Marcin Borowiec

Czytając różne informacje o kartach graficznych możemy się dowiedzieć, że ich wydajność jest wielokrotnie większa od odpowiednich im cenowo procesorów x86. Często można też zobaczyć liczby w postaci nawet ponad 2TFLOPSów dla GPU i maksymalnie 100GFLOPS dla najszybszych CPU. Dzisiaj postaram się opowiedzieć o teoretycznej wydajności różnych układów i tego w jaki sposób można ją obliczyć. Natomiast w kolejnych postach opisze różnice w architekturze CPU i GPU AMD i NVidii i to jak wpływają one na poziom wykorzystania teoretycznej wydajności.

Zanim jednak przejdę do sedna sprawy muszę napisać pewne uaktualnienie do mojego poprzedniego posta o podstawach GPU. Po jego publikacji AMD wydało w końcu sterowniki (Catalyst 9.12 Hotfix) z obsługą DirectCompute 4.1 dla Radeonów 48xx i 47xx. Niestety część osób będzie zawiedziona. Nadal brakuje obsługi DirectCompute w Radeonach 46xx i słabszych, które również były często kupowane. Podobnie lista rozszerzeń OpenCL dla mojego Radeona 4850 jest ciągle pusta. Pisząc o podstawach GPU pominąłem także jedną ale do niektórych zastosowań ważną rzecz. Radeony 47xx, 48xx, 58xx posiadają obsługę obliczeń na liczbach podwójnej precyzji. NVidia z kolei podwójną precyzje posiada tylko w układach opartych na GT200 i jej implementacja jest dość wolna. Dokładniejsze liczby podam w dalszej części postu.

Na początku postu wspomniałem o FLOPSach i myślę że warto napisać najpierw co to jest. Dość dobrą definicje można przeczytać na wikipedii: http://pl.wikipedia.org/wiki/FLOPS. Jednak na powyższej stronie nie ma wyraźnie zaznaczonej jednej sprawy. Wydajność we FLOPS jednej i tej samej maszyny może dotyczyć różnych rzeczy: (i mieć w zależności od tego inną wartość)

  1. Teoretycznej wydajności wszystkich jednostek obliczeniowych opierając się przede wszystkim na operacjach dodawania i mnożenia bez takich operacji jak np. shift.
  2. Wydajności, która została uzyskana w określonym benchmarku. Taka wydajność będzie przeważnie niższa od wartości z pkt1 w związku z np czekaniem na pobranie danych z pamięci, niemożnością wykorzystania specyficznych jednostek przystosowanych tylko do pewnych operacji czy niemożliwością równoległego wykorzystania kilku jednostek z powodu zależnych od siebie operacji. Podobnie jak w poprzednim przypadku liczone są operacje dodawania i mnożenia wykonywane przez benchmark.

Opisywanie teoretycznej wydajności zaczne od dzisiejszych najmocniejszych jednostek CPU. Weźmy dla przykładu czterordzeniowy Core i7. Każdy z rdzeni posiada jedną jednostkę odpowiedzialną za dodawanie na 128bitowych danych i jedną jednostke do mnożenia na 128bitowych danych co oznacza mniej więcej maksymalnie:

  • 2 operacje x87 na rozszerzonych 80bitowych liczbach zmiennoprzecinkowych w jednym cyklu zegara
  • 4 operacje SSE na 64bitowych liczbach zmiennoprzecinkowych (podwójna precyzja) w jednym cyklu zegara
  • 8 operacji SSE na 32bitowych liczbach zmiennoprzecinkowych (pojedyncza precyzja) w jednym cyklu zegara

Co dla czterordzeniowego Core i7 o taktowaniu 3GHz oznacza wydajność odpowiednio 24GFlops, 48GFlops, 96GFlops dla danego typu operacji czyli całkiem sporo.

Dalej mamy np układ GeForce GTX 280. Do dyspozycji mamy tutaj 30 multiprocesorów, z których każdy posiada 8 jednostek SP (Shader Processor) o wydajności 2 operacje pojedynczej precyzji (jedna operacja mad czyli jedna operacja mnożenia i jedna dodawania (a*b+c)) na jeden cykl i dodatkowo 2 jednostki SFU (Special Function Unit), każda o wydajności 1 operacja specjalna (sin, log, sqrt) lub 4 operacje mnożenia na cykl (dzięki tzw. ‘dual issue’). Razem daje to 30*(8*2 + 2*4) = 720 operacji na jeden cykl dla całego GPU. Mnożąc to przez częstotliwość “szaderów” czyli 1296MHz otrzymujemy 933GFLOPS!

Niestety wydajność tego samego układu w podwójnej precyzji jest znacznie mniejsza. Każdy multiprocesor potrafi wykonać tylko jedną operacje MAD (liczoną jako dwie operacje).  Jednostki SFU nie operują w ogóle na 64bitowych liczbach zmiennoprzecinkowych. Co przekłada się na “tylko” 30*2 = 60 operacji na jeden cykl. Przy częstotliwości 1296MHz daje to ok 78GFlops.

Konkurencyjny Radeon 4870 wydany w podobnym czasie co GTX280 posiada 160 SP pogrupowanych po 16SP w jednym SIMD Engine. Każdy shader składa się z 5 jednostek SPu potrafiących wykonać jedną operacje mad na cykl (także liczone jako 2 operacje na cykl). Dodatkowo jedna z tych 5 jednostek potrafi wykonać jedną instrukcję specjalną na cykl (zamiast instrukcji mad). Razem daje to 10 * 16 * 5 * 2 = 1600 operacji na cykl czyli 1.2TFlops przy częstotliwości 750MHz. Jak widać teoretyczna wydajność Radeona 4870 jest większa niż GeForca GTX280.

W przypadku podwójnej precyzji przewaga Radeona znacznie się zwiększa, gdyż każdy SP potrafi wykonać jedną instrukcje MAD na cykl co daje 10*16*2=320 operacji na cykl co przekłada się na 240GFlops. Ponad 3x więcej niż GTX280! O ile w przypadku pojedynczej precyzji GeForce “nadrabia architekturą” i w wielu przypadkach jest szybszy od Radeona to w double precision trzeba uznać wyższość Radeona. Od razu jednak przypomnę że, aby wydobyć pełnię mocy Radeona trzeba wykorzystać niezbyt wygodny CAL.

Najnowszy Radeon 5870 z punktu widzenia dzisiejszego tematu postu jest podwojonym Radeonem 4870 (zamiast 10 SIMD Engine mamy ich 20) co oznacza 3200 operacji na cykl dla pojedynczej precyzji i 640 operacji dla podwójnej precyzji. Przy zwiększonym taktowaniu rdzenia do 850MHz otrzymujemy 2.72TFlops dla pojedynczej precyzji i 544GFlops dla podwójnej.

Niestety ciągle nie wiadomo jakie parametry będzie miał zapowiadany układ GF100 od NVidii. Brakuje informacji o częstotliwości rdzeni i dokładniejszych informacji o jednostkach SFU. Można jednak spodziewać się wyraźnie wyższej wydajności od Radeona 5870 w podwójnej precyzji i wyraźnie niższej wydajności w pojedynczej precyzji.

Na koniec warto przypomnieć że teoretyczna maksymalna wydajność to nie wszystko. Układy CPU mimo iż teoretycznie wielokrotnie wolniejsze od GPU w wielu zastosowaniach są niezastępowalne. Podobnie Radeon 4870 teoretycznie szybszy od GeForca GTX 280 przegrywa z nim w prawie każdej grze. W kolejnych postach postaram się opisać różnice w architekturze, które decydują o wydajności w realnych zastosowaniach.

GPGPU – podstawowe informacje

Listopad 28th, 2009 Marcin Borowiec

Zauważyłem ostatnio, że popularność GPGPU wśród użytkowników/programistów zaczęła gwałtownie wzrastać. Niestety wielu z nich nie wie co to tak na prawdę jest, jak z tego skorzystać, co trzeba posiadać (sprzęt, oprogramowanie) i jakie ma to możliwości. Postanowiłem więc przybliżyć nieco idę i możliwości GPGPU. Nie zamierzam jednak tworzyć tutaj jakiegoś kursu (przynajmniej na razie).

GPGPU to jak sama nazwa wskazuje możliwość wykorzystania procesorów graficznych do obliczeń ogólnego przeznaczenia. Idea GPGPU zaczeła się rodzić po udostępnieniu kart graficznych zgodnych z DirectX9 (głównie DirectX9c). Operacje zmiennoprzecinkowe z 32bitową precyzją, sensowna długość kodu shadera, dynamiczne instrukcje warunkowe w kodzie, pętle mogły dać początek GPGPU. Niestety w tamtym czasie jedynym możliwym sposobem wykorzystania kart graficznych było napisanie programu z wykorzystaniem DirectX czy OpenGL. Biblioteki te oprócz bezpośredniego wyświetlania obrazu na ekranie posiadają możliwość generowania wyników do tekstury. Programista mógł więc wysłać do karty graficznej dane w postaci tablicy danych (jako tekstura czy bufor wierzchołków), wygenerować wynik i pobrać wynik (teksturę). Do pewnych specyficznych operacji coś takiego wystarczyło, kiedyś czytałem o prostych operacjach na próbkach dźwiękowych dzięki takiemu chwytowi. Niestety do wielu zastowań to zdecydowanie za mało.

Kolejnym etapem było stworzenie kart graficznych z tzw. zunifikowanymi jednostkami cieniowania. Mowa tu seriach GeForce 8xxx i RadeonHD 2xxx z czego produkty NVidi były w kontekście GPGPU bardziej zaawansowane i nadal są, ale o tym nieco później. NVidia razem z premierą serii 8 GeForce’ów udostępniła technologię CUDA. Technologia ta pozwalała w dość prosty sposób wykorzystać moce drzemiące w karcie graficznej. CUDA tworzone było przede wszystkim do obliczeń GPGPU więc nie spotkamy tutaj niepotrzebnych ograniczeń. AMD(ATI) też myślało o czymś podobnym, najpierw zaczeli od ATI CTM (Close To Metal), które dość szybko anulowali bo zrozumieli że nie tędy droga a później stworzyli ATI Stream.

Obecnie interfejsów do programowania kart graficznych jest dość dużo. Poniżej wymienie najważniejsze z nich:

  • C for CUDA czyli programowanie w zmodyfikowanym przez nvidie języku C. Rozwiązanie do tej pory najpopularniejsze, procentowy rozkład użycia tej technologii może w przyszłości nieco zmaleć za sprawą interfejsów otwartych takich jak OpenCL czy DirectCompute.
  • CUDA PTX czyli możliwość pisania programów na GPU NVidii w asemblerze, w pewnych przypadkach sensownym może być utworzenie programu w C, skompilowanie go a później ręczna optymalizacja w asemblerze. Będąc scisłym, PTX nie jest asemblerem wykonywanym bezpośrednio przez GPU. PTX przed wykonaniem musi być przetłumaczony na kod maszynowy konkretnego modelu GPU, na którym będzie uruchomiony program.
  • Brook++ składnik ATI Stream w wersjach 1.x, stosunkowo prosty sposób programowania GPU, łatwy do użycia ale zarazem o niewielkich możliwościach (w porównaniu np do C for CUDA), trudno w Brook++ wykorzystać pełnię mocy Radeonów HD5xxx. Właśnie dlatego AMD przestaje oficjalnie wspierać Brook++ w ATI Stream 2.x. Mimo iż zarzekają się że projekt dalej będzie wspierany, z tym że od teraz jako oddzielny projekt open source to można uznać że jest to w tej chwili ślepa uliczka. AMD chce się teraz skupić na bardziej uniwersalnych interfejsach (OpenCL i DirectCompute)
  • AMD CAL z asemblerem AMD IL jest odpowiednikiem PTX od Nvidii. Asembler daje największe możliwości na zaoptymalizowanie kodu jednak ze wględu na poziom trudności jest bardzo rzadko stosowany.
  • OpenCL, otwarty standard rozwijany przez Khronos Group (przez tą samą grupę, która odpowiada za OpenGL), Każdy z producentów (AMD, NVidia) dostarcza implementację tego interfejsu. Khronos Group zajmuje się tylko certyfikacją gotowych bibliotek. OpenCL posiada także funkcje, które mają umożliwić lepszą współpracę z biblioteką OpenGL: m.in edycję buforów wierzchołków OpenGL z poziomu OpenCL.
  • DirectCompute (element DirectX11), biblioteka przygotowana we współpracy producentów kart graficznych z firmą Microsoft. Charakteryzuje się bardzo dobrą współpracą z innymi elementami DirectX.

Teoretycznie jest możliwe napisanie programu pod GPU nie posiadając odpowiedniego akceleratora u siebie. C for CUDA posiada możliwość emulacji napisanego kodu na CPU, OpenCL posiada implementacje także na CPU (ja przynajmniej znam jedną od AMD), DirectCompute można uruchomić poprzez urządzenie referencyjne (które jest bardzo wolne, WARP niestety nie obsługuje DirectCompute). W rzeczywistości sytuacje, które powstają podczas wykorzystania GPU nijak nie można zasymulować na CPU, tak więc bardzo mocno polecam zaopatrzyć  się w odpowiednią kartę graficzną. Jaką? Dla mnie jedynymi sensownymi układami są najnowsza seria Radeonów HD 5xxx i kart NVidii zgodnych z ComputeCapability przynajmniej 1.1 (czyli seria GTX 2xx, 9xxx i częściowo 8xxx, bez słynnego G80) a także odpowiadające im modele układów Tesla i FireStream. Dlaczego akurat takie karty?

  • Radeon HD seria 2xxx i HD seria 3xxx wypada bardzo ubogo pod względem wydajności obliczeniowej, obsługiwanych bibliotek jak i funkcji wbudowanych w GPU. Jeśli ktoś nie daje Ci za darmo takiej karty to jej nie bierz. Brak m.in obsługi OpenCL i DirectCompute!
  • Radeon HD seria 4xxx to układy pełne paradoksów. W karty wbudowano pamięć LDS – Local Data Store. NVidia coś podobnego (Shared Memory) miała już w pierwszych układach GeForce 8! Najciekawsze jest jednak to że Brook++ z racji swojej specyfikacji nie jest w stanie wykorzystać odpowiednio LDS, OpenCL mimo iż obsługiwany przez serie 4xxx to LDS nie jest zgodny z OpenCL i nie jest w ogóle wykorzystywany. DirectCompute 4.0, który teoretycznie jest kompatybilny z LDS nie jest jeszcze dostępny bo AMD nie przygotowała odpowiednich sterowników. Oprócz LDS seria 4xxx posiada pamięć GDS – Global Data Storage, która w zasadzie też nie może być wykorzystana, ponieważ Radeon 4xxx nie potrafi odpowiednio zsynchronizować dostępu do niej. Łącznie 176KB bardzo szybkiej pamięci wbudowanej wewnątrz układu nie może być wykorzystane. Brawo AMD!
  • Radeon HD seria 5xxx, w końcu układ z  bardziej zaawansowaną pamięcią LDS (pełny odczyt i zapis przez każdy wątek z grupy), obsługą operacji atomowych, obsługą konkretnego API do programowania GPU (OpenCL i DirectCompute). Tylko dlaczego dopiero teraz?
  • Jeśli chodzi o NVidię to tutaj każdy układ zgodny z CUDA jest sensowny. Każdy z tych układów dzięki nowszym sterownikom obsłuży także OpenCL i DirectCompute włącznie z wprowadzonym kilka lat temu do sprzedaży GeForcem 8800! Ja jednak polecam zaopatrzyć się w nieco nowszy układ (coś na bazie G92 albo najlepiej GT200), który posiada obsługę instrukcji atomowych.

Wrócę jeszcze na chwilę do interfejsów programowania. Myślę że można tutaj przyjąć następujący algorytm wyboru:

  • Rozszerzanie gry w DirectX o zaawansowane efekty: DirectCompute
  • Rozszerzanie gry w OpenGL o zaawansowane efekty: OpenCL
  • Wykorzystanie GPU do obliczeń innych niż graficzne: OpenCL
  • Specjalistyczne zadania niewymagające wspierania wszystkich producentów GPU: Najnowszy układ GeForce/Quadro/Tesla, C for CUDA lub ewentualnie PTX gdy konieczna jest maksymalna optymalizacja.

Na koniec pozostała jeszcze jedna ważna sprawa. Jak bardzo obecne GPGPU jest elastyczne, jakiego typu programy można uruchamiać na kartach graficznych?

  1. Obecnie żaden dotychczasowy program pisany z myślą o CPU nie może być bezpośrednio przeniesiony na GPU bez jakiejkolwiek modyfikacji kodu. Chodzi mi tutaj o programy pisane w C++, C# i innych tego typu językach.
  2. Algorytm przenoszony na GPU musi być wielowątkowy. Chodzi tutaj o rząd kilku tysięcy wątków. Co więcej każdy z tych wątków musi przetwarzać ten sam kod więc mamy tutaj do czynienia wyłącznie ze współbieżnością na poziomie danych. Oczywiście każdy wątek ma swój kontekst i wie czy powinien przetwarzać tą czy tą gałęź kodu (w przypadku pętli i instrukcji warunkowych). To ograniczenie będzie częściowo zniesione w nowym GPU NVidii “Fermi”, który zadebiutuje w przyszłym roku. Teoretycznie każdy multiprocesor będzie mógł wykonywać inny kernel (tak popularnie nazywana jest funkcja wywołana na GPU) co oznacza kilka-kilkanaście niezależnych grup wątków wykonujących inny kod.
  3. Z poziomu GPU program nie ma dostępu do pamięci głównej komputera, nie ma dostępu do urządzeń wejścia/wyjścia komputera. Część programu działająca po stronie CPU musi wysłać dane do urządzenia, uruchomić funkcję na tym urządzeniu i pobrać wyniki z urządzenia. Jeśli funkcja na CPU uzna że musi uaktualnić dane to funkcja na GPU musi zostać przerwana, nowe dane muszą zostać dostarczone i funkcja musi być ponownie uruchomiona.
  4. GPU nie obsługuje rekurencji, każdy algorytm musi być zapisany w wersji iteracyjnej.
  5. GPU nie obsługuje zaawansowanych sposobów synchronizacji wątków, mimo wszystko operacje atomowe (choć niedostępne w niektórych GPU) i bariery na grupy wątków wystarczają do zbudowania efektywnej współpracy pomiędzy wątkami. Wątki w grupie mogą odczytywać i zapisywać wspólną pamięć dzieloną. Wszystkie wątki na urządzeniu mogą odczytywać i zapisywać wspólną pamięć globalną.
  6. GPU nie posiada zaawansowanego mechanizmu cache (chociaż wspomniany wcześniej układ “Fermi” ma taki cache posiadać). Obecnie trzeba sobie pomóc trzema elementami dającymi namiastkę cache: odczyt/zapis tabel dwuwymiarowych ze specjalnym tylko do tego dedykowanym małym cache; odczyt z pamięci stałej (pamięć globalna oznaczona jako nie zmieniana podczas pojedynczego wywołania kernela), do tego też jest specjalny cache; stworzenie ręcznie zarządzanego ogólnego cache poprzez wykorzystanie wbudowanej pamięci dzielonej dla grupy wątków.

Przykładów wykorzystania GPU można szukać np. na stronie NVidii. Ja dodam że istnieje możliwość przeniesienia sztucznej inteligencji Paper Balla na GPU, robiłem już małe testy ;)

Post wyszedł dość długi a ja nie napisałem wszystkiego czego chciałem. Mimo wszystko na razie na tym zakończę, a do tematu wrócę w którymś z kolejnych mini artykułów.

GPGPU – ciekawe linki

Wrzesień 24th, 2008 Marcin Borowiec

GPGPU (General-purpose computing on graphics processing units czyli wykorzystanie kart graficznych do obliczeń ogólnego przeznaczenia) staje się coraz bardziej popularne. Przyczyniła się do tego mocno nvidia reklamując swoje “cuda”. AMD jest nieco w tyle ale dzielnie tworzy swoje Stream SDK. Ostatnio sam zabrałem się za zgłębianie tajemnic procesorów graficznych i api, które przygotowali ich producenci. Zebrałem przy tej okazji kilka ciekawych linków.

Oczywiście podstawowym źródłem informacji są strony: NVIDIA CUDA i AMD Stream. Na początek polecam AMD Stream SDK User Guide i Building a High Level Language Compiler For GPGPU ? PLDI?08 Tutorial. Dokumentacja od AMD wydaje mi się bardziej przyjazna dla początkującego niż ta od nvidii. Przede wszystkim AMD przedstawia architekturę swoich kart graficznych i na tym oparty jest opis API Stream SDK. Pozwala to lepiej zrozumieć dlaczego programy pod GPU piszę się tak a nie inaczej i jak należy programować żeby uzyskać wysoką wydajność. Z kolei nvidia serwuje nam czysty opis API technologii CUDA. Znajdziemy tam kilka nowych pojęć, które wprowadzono aby uporządkować informację o programowaniu ich GPU. Szkoda tylko że nie opisali jak to się ma do architektury GeForce. Czytając dokumentację od AMD warto wspomóc się opisami: RV770 (1) i RV770 (2) (RV770 to układ znajdujący się na kartach Radeon 4850 i Radeon 4870). Przytoczone wcześniej AMD User Guide wspomina głównie o poprzedniku (układzie RV670 znajdującym się na kartach Radeon 3870). Podstawą do programowania w technologii CUDA jest oczywiście NVIDIA Programming Guide. Jeśli po przeczytaniu zastanawiacie się co to jest do cholery ten “warp” polecam przeczytanie artykułu: NVIDIA’s 1.4 Billion Transistor GPU: GT200 Arrives as the GeForce GTX 280 & 260, szczególnie strony drugiej i czwartej.

Jeśli macie jakieś inne ciekawe linki dotyczące tematu GPGPU dopiszcie je w komentarzach :)