Obliczenia włączone GPU

Technologia CUDA (Compute Unified Device Architecture) to architektura oprogramowania i sprzętu, która umożliwia przetwarzanie przy użyciu procesorów graficznych NVIDIA obsługujących technologię GPGPU (arbitralne przetwarzanie na kartach graficznych). Architektura CUDA po raz pierwszy pojawiła się na rynku wraz z wydaniem układu NVIDIA ósmej generacji - G80 i jest obecna we wszystkich kolejnych seriach układów graficznych, które są używane w rodzinach akceleratorów GeForce, ION, Quadro i Tesla.

CUDA SDK umożliwia programistom implementację, w specjalnym uproszczonym dialekcie języka programowania C, algorytmów, które mogą być uruchamiane na procesorach graficznych NVIDIA i zawierają specjalne funkcje w tekście programu C. CUDA daje programiście możliwość, według własnego uznania, organizowania dostępu do zestawu instrukcji akceleratora graficznego i zarządzania jego pamięcią, organizowania na nim złożonych obliczeń równoległych.

Fabuła

W 2003 roku Intel i AMD rywalizowały ze sobą o najwięcej potężny procesor. Z biegiem lat, w wyniku tego wyścigu, częstotliwość taktowania znacznie wzrosła, zwłaszcza po wydaniu Intela Pentium 4.

Po wzroście częstotliwości taktowania (w latach 2001-2003 częstotliwość taktowania Pentium 4 podwoiła się z 1,5 do 3 GHz), a użytkownicy musieli zadowolić się dziesiątymi częściami gigaherca, które producenci wprowadzali na rynek (od 2003 do 2005 częstotliwości taktowania zwiększona z 3 do 3,8 GHz).

Architektury zoptymalizowane pod kątem wysokich częstotliwości taktowania, takie jak Prescott, również zaczęły doświadczać trudności, nie tylko w produkcji. Producenci chipów stanęli przed wyzwaniami związanymi z pokonaniem praw fizyki. Niektórzy analitycy przewidywali nawet, że prawo Moore'a przestanie działać. Ale tak się nie stało. Pierwotne znaczenie prawa jest często błędnie przedstawiane, ale odnosi się ono do liczby tranzystorów na powierzchni rdzenia krzemowego. Przez długi czas wzrostowi liczby tranzystorów w CPU towarzyszył odpowiedni wzrost wydajności - co prowadziło do zniekształcenia znaczenia. Ale potem sytuacja się skomplikowała. Projektanci architektury procesora podeszli do prawa redukcji wzmocnienia: liczba tranzystorów, które trzeba było dodać, aby uzyskać pożądany wzrost wydajności, stawała się coraz większa, prowadząc do ślepego zaułka.

Powód, dla którego producenci procesorów graficznych nie napotkali tego problemu, jest bardzo prosty: procesory są zaprojektowane tak, aby uzyskać najlepszą wydajność w strumieniu instrukcji, które przetwarzają różne dane (zarówno liczby całkowite, jak i liczby zmiennoprzecinkowe), wykonują losowy dostęp do pamięci itp. d. Do tej pory programiści starali się zapewnić większą równoległość instrukcji - to znaczy wykonywać równolegle jak najwięcej instrukcji. Tak więc na przykład w Pentium pojawiło się wykonywanie superskalarne, gdy w określonych warunkach możliwe było wykonanie dwóch instrukcji na zegar. Pentium Pro otrzymał wykonanie instrukcji poza kolejnością, co pozwoliło zoptymalizować wydajność jednostek obliczeniowych. Problem polega na tym, że równoległe wykonywanie sekwencyjnego strumienia instrukcji ma oczywiste ograniczenia, więc ślepe zwiększanie liczby jednostek obliczeniowych nie daje zysku, ponieważ przez większość czasu będą one nadal bezczynne.

Działanie GPU jest stosunkowo proste. Polega na wzięciu grupy wielokątów z jednej strony i wygenerowaniu grupy pikseli z drugiej. Wielokąty i piksele są od siebie niezależne, dzięki czemu można je przetwarzać równolegle. Tym samym w GPU można przeznaczyć dużą część kryształu na jednostki obliczeniowe, które w przeciwieństwie do CPU będą faktycznie wykorzystywane.

GPU różni się od CPU nie tylko tym. Dostęp do pamięci w GPU jest mocno sprzężony - jeśli odczytany zostanie teksel, to po kilku cyklach zostanie odczytany teksel sąsiedni; gdy piksel zostanie zapisany, sąsiedni zostanie zapisany po kilku cyklach. Inteligentnie organizując pamięć, można uzyskać wydajność zbliżoną do teoretycznej przepustowości. Oznacza to, że GPU, w przeciwieństwie do CPU, nie wymaga ogromnej pamięci podręcznej, ponieważ jego rolą jest przyspieszenie operacji teksturowania. Wystarczy kilka kilobajtów zawierających kilka tekseli używanych w filtrach dwu- i trójliniowych.

Pierwsze obliczenia na GPU

Pierwsze próby takiej aplikacji ograniczały się do wykorzystania niektórych funkcji sprzętowych, takich jak rasteryzacja i buforowanie Z. Ale w obecnym stuleciu, wraz z pojawieniem się shaderów, zaczęli przyspieszać obliczanie macierzy. W 2003 roku SIGGRAPHowi przydzielono osobną sekcję do obliczeń GPU i nazwano ją GPGPU (ang. General-Purpose Computation na GPU).

Najbardziej znanym BrookGPU jest kompilator języka programowania Brook, zaprojektowany do wykonywania niegraficznych obliczeń na GPU. Przed jego pojawieniem się programiści wykorzystujący do obliczeń możliwości chipów wideo wybrali jeden z dwóch popularnych API: Direct3D lub OpenGL. To poważnie ograniczyło użycie GPU, ponieważ grafika 3D wykorzystuje shadery i tekstury, o których programiści równolegli nie muszą wiedzieć, używają wątków i rdzeni. Brook był w stanie ułatwić im zadanie. Te rozszerzenia przesyłania strumieniowego do języka C, opracowane na Uniwersytecie Stanforda, ukryły interfejs API 3D przed programistami i zaprezentowały układ wideo jako równoległy koprocesor. Kompilator przeanalizował plik .br z kodem C++ i rozszerzeniami, tworząc kod połączony z biblioteką obsługującą DirectX, OpenGL lub x86.

Pojawienie się Brooka wzbudziło zainteresowanie NVIDII i ATI, a dodatkowo otworzyło zupełnie nowy sektor - komputery równoległe oparte na chipach wideo.

Co więcej, niektórzy badacze z projektu Brook przenieśli się do zespołu programistów NVIDIA, aby wprowadzić strategię obliczeń równoległych sprzętowo-programowych, otwierając nowy udział w rynku. Główną zaletą tej inicjatywy NVIDIA było to, że programiści doskonale znają wszystkie możliwości swoich procesorów graficznych w najdrobniejszych szczegółach i nie ma potrzeby korzystania z graficznego API, a ze sprzętem można pracować bezpośrednio za pomocą sterownika. Efektem wysiłków tego zespołu jest NVIDIA CUDA.

Obszary zastosowania obliczeń równoległych na GPU

Gdy obliczenia są przenoszone na GPU, w wielu zadaniach przyspieszenie osiąga się 5-30 razy w porównaniu z szybkimi procesorami ogólnego przeznaczenia. Największe liczby (rzędu przyspieszenia 100x, a nawet więcej!) są osiągane na kodzie, który nie jest zbyt dobrze przystosowany do obliczeń z użyciem bloków SSE, ale jest całkiem wygodny dla GPU.

To tylko kilka przykładów przyspieszenia kodu syntetycznego na GPU w porównaniu z kodem wektorowym SSE na CPU (według NVIDIA):

Mikroskopia fluorescencyjna: 12x.

Dynamika molekularna (obliczona siła niezwiązana): 8-16x;

Elektrostatyka (bezpośrednie i wielopoziomowe sumowanie kulombowskie): 40-120x i 7x.

Tabela, którą NVIDIA pokazuje we wszystkich prezentacjach, która pokazuje szybkość procesorów graficznych w stosunku do procesorów.

Lista głównych zastosowań, w których wykorzystywane są obliczenia GPU: analiza i przetwarzanie obrazów i sygnałów, symulacja fizyki, matematyka obliczeniowa, biologia obliczeniowa, obliczenia finansowe, bazy danych, dynamika gazów i cieczy, kryptografia, radioterapia adaptacyjna, astronomia, przetwarzanie dźwięku, bioinformatyka, symulacje biologiczne, wizja komputerowa, eksploracja danych, kino cyfrowe i telewizja, symulacje elektromagnetyczne, systemy informacji geograficznej, zastosowania wojskowe, planowanie górnicze, dynamika molekularna, rezonans magnetyczny (MRI), sieci neuronowe, badania oceanograficzne, fizyka cząstek elementarnych, symulacja składania białek, chemia kwantowa, śledzenie promieni, obrazowanie, radar, symulacja zbiorników, sztuczna inteligencja, analiza danych satelitarnych, eksploracja sejsmiczna, chirurgia, ultradźwięki, wideokonferencje.

Korzyści i ograniczenia CUDA

Z punktu widzenia programisty potok graficzny to zestaw etapów przetwarzania. Blok geometrii generuje trójkąty, a blok rasteryzacji generuje piksele wyświetlane na monitorze. Tradycyjny model programowania GPGPU wygląda następująco:

Aby przenieść obliczenia na GPU w ramach takiego modelu, potrzebne jest specjalne podejście. Nawet dodanie dwóch wektorów element po elemencie będzie wymagało narysowania kształtu na ekranie lub w buforze poza ekranem. Rysunek jest rasteryzowany, kolor każdego piksela jest obliczany zgodnie z danym programem (pixel shader). Program odczytuje dane wejściowe z tekstur dla każdego piksela, dodaje je i zapisuje w buforze wyjściowym. A wszystkie te liczne operacje są potrzebne do tego, co jest napisane w jednym operatorze w konwencjonalnym języku programowania!

Dlatego wykorzystanie GPGPU do obliczeń ogólnego przeznaczenia ma ograniczenie w postaci zbyt dużej złożoności, której mogą się nauczyć programiści. Tak, i jest wystarczająco dużo innych ograniczeń, ponieważ pixel shader to tylko formuła zależności ostatecznego koloru piksela od jego współrzędnych, a pixel shader to język do pisania tych formuł ze składnią podobną do C. Wczesne metody GPGPU to sprytna sztuczka, która pozwala wykorzystać moc GPU, ale bez żadnej wygody. Tam dane są reprezentowane przez obrazy (tekstury), a algorytm jest reprezentowany przez proces rasteryzacji. Należy zwrócić uwagę na bardzo specyficzny model pamięci i wykonania.

Architektura sprzętowa i programowa NVIDIA do obliczeń na procesorach graficznych firmy NVIDIA różni się od poprzednich modeli GPGPU tym, że umożliwia pisanie programów dla procesorów graficznych w prawdziwym C ze standardową składnią, wskaźnikami i koniecznością posiadania minimum rozszerzeń, aby uzyskać dostęp do zasobów obliczeniowych układów wideo. CUDA nie zależy od graficznych interfejsów API i ma pewne funkcje zaprojektowane specjalnie do przetwarzania ogólnego przeznaczenia.

Przewaga CUDA nad tradycyjnym podejściem do obliczeń GPGPU

CUDA zapewnia dostęp do 16 KB pamięci współdzielonej na wieloprocesor, co może być wykorzystane do zorganizowania pamięci podręcznej o większej przepustowości niż pobieranie tekstur;

Wydajniejszy transfer danych między systemem a pamięcią wideo;

Nie ma potrzeby korzystania z graficznych interfejsów API z nadmiarowością i narzutem;

Liniowe adresowanie pamięci oraz gromadzenie i rozpraszanie, możliwość pisania na dowolne adresy;

Wsparcie sprzętowe dla operacji na liczbach całkowitych i bitach.

Główne ograniczenia CUDA:

Brak obsługi rekurencji dla funkcji wykonywalnych;

Minimalna szerokość bloku to 32 wątki;

Zamknięta architektura CUDA należąca do firmy NVIDIA.

Słabości programowania z poprzednimi metodami GPGPU polegają na tym, że metody te nie używają jednostek wykonawczych Vertex Shader w poprzednich architekturach niezunifikowanych, dane są przechowywane w teksturach i wyprowadzane do bufora poza ekranem, a algorytmy wieloprzebiegowe używają jednostek cieniowania pikseli. Ograniczenia GPGPU obejmują: niewystarczająco wydajne wykorzystanie możliwości sprzętowych, ograniczenia przepustowości pamięci, brak operacji rozproszonych (tylko gromadzenie), obowiązkowe użycie graficznego API.

Główne zalety CUDA nad poprzednimi metodami GPGPU wynikają z faktu, że architektura ta została zaprojektowana tak, aby: efektywne wykorzystanie obliczeń niegraficznych na GPU i wykorzystuje język programowania C, bez konieczności przenoszenia algorytmów do postaci dogodnej dla koncepcji potoku graficznego. Oferty CUDA nowy sposób Obliczenia na GPU, które nie wykorzystują graficznych interfejsów API i oferują losowy dostęp do pamięci (scatter lub collect). Taka architektura jest wolna od wad GPGPU i wykorzystuje wszystkie jednostki wykonawcze, a także rozszerza możliwości o matematykę liczb całkowitych i operacje przesunięcia bitowego.

CUDA otwiera niektóre funkcje sprzętowe niedostępne w graficznych interfejsach API, takie jak pamięć współdzielona. Jest to niewielka ilość pamięci (16 kilobajtów na wieloprocesor), do której mają dostęp bloki wątków. Umożliwia buforowanie najczęściej używanych danych i może zapewnić więcej wysoka prędkość, w porównaniu do korzystania z pobierania tekstur w tym zadaniu. To z kolei zmniejsza czułość przepustowości algorytmów równoległych w wielu aplikacjach. Na przykład jest przydatny do algebry liniowej, szybkiej transformacji Fouriera i filtrów przetwarzania obrazu.

Wygodniejszy dostęp do CUDA i pamięci. Kod programu w graficznym API wyprowadza dane w postaci 32 wartości zmiennoprzecinkowych o pojedynczej precyzji (wartości RGBA jednocześnie w ośmiu celach renderowania) w predefiniowanych obszarach, a CUDA obsługuje zapis rozproszony - nieograniczona liczba rekordów pod dowolnym adresem . Takie zalety umożliwiają wykonanie niektórych algorytmów na GPU, których nie można efektywnie zaimplementować metodami GPGPU opartymi na graficznym API.

Ponadto graficzne API muszą przechowywać dane w teksturach, co wymaga wcześniejszego upakowania dużych tablic w tekstury, co komplikuje algorytm i wymusza zastosowanie specjalnego adresowania. A CUDA umożliwia odczyt danych pod dowolnym adresem. Kolejną zaletą CUDA jest zoptymalizowana komunikacja między CPU i GPU. A dla programistów, którzy chcą uzyskać dostęp do niskiego poziomu (na przykład podczas pisania innego języka programowania), CUDA oferuje możliwość programowania w języku asemblera niskiego poziomu.

Wady CUDA

Jedną z niewielu wad CUDA jest jej słaba przenośność. Ta architektura działa tylko na układach wideo tej firmy, a nie na wszystkich, ale począwszy od serii GeForce 8 i 9 oraz odpowiadających im Quadro, ION i Tesla. NVIDIA podaje liczbę 90 milionów chipów wideo zgodnych z CUDA.

Alternatywy dla CUDA

Ramy do pisania programy komputerowe związane z przetwarzaniem równoległym na różnych procesorach graficznych i centralnych. Framework OpenCL zawiera język programowania oparty na standardzie C99 oraz interfejs programowania aplikacji (API). OpenCL zapewnia równoległość na poziomie instrukcji i danych oraz jest implementacją techniki GPGPU. OpenCL to całkowicie otwarty standard i nie ma żadnych opłat licencyjnych za korzystanie z niego.

Celem OpenCL jest uzupełnienie OpenGL i OpenAL, które są otwartymi standardami branżowymi grafiki komputerowej i dźwięku 3D, poprzez wykorzystanie mocy GPU. OpenCL jest rozwijany i utrzymywany przez Khronos Group, konsorcjum non-profit, które obejmuje wiele dużych firm, w tym Apple, AMD, Intel, nVidia, Sun Microsystems, Sony Computer Entertainment i inne.

CAL/IL (warstwa abstrakcji obliczeniowej/język pośredni)

Technologia ATI Stream to zestaw sprzętu i technologie oprogramowania, które umożliwiają wykorzystanie procesorów graficznych AMD w połączeniu z procesorem do przyspieszenia wielu aplikacji (nie tylko graficznych).

Obszary zastosowań ATI Stream to aplikacje wymagające zasobów obliczeniowych, takie jak: analiza finansowa lub przetwarzanie danych sejsmicznych. Zastosowanie procesora strumieniowego umożliwiło 55-krotne zwiększenie szybkości niektórych obliczeń finansowych w porównaniu do rozwiązania tego samego problemu przy użyciu tylko procesor.

NVIDIA nie uważa technologii ATI Stream za bardzo silnego konkurenta. CUDA i Stream to dwie różne technologie, które są na różnych poziomach rozwoju. Programowanie produktów ATI jest znacznie trudniejsze - ich język przypomina bardziej asembler. Z drugiej strony CUDA C jest językiem znacznie wyższym. Pisanie na nim jest wygodniejsze i łatwiejsze. Dla dużych firm deweloperskich jest to bardzo ważne. Jeśli mówimy o wydajności, to widzimy, że jej szczytowa wartość w produktach ATI jest wyższa niż w rozwiązaniach NVIDIA. Ale znowu wszystko sprowadza się do tego, jak uzyskać tę moc.

DirectX11 (DirectCompute)

Interfejs programowania aplikacji, który jest częścią DirectX, zestawu interfejsów API firmy Microsoft, który jest przeznaczony do uruchamiania na komputerach zgodnych z IBM PC z uruchomionymi system operacyjny Rodzina Microsoft Windows. DirectCompute jest przeznaczony do wykonywania obliczeń ogólnego przeznaczenia na procesorach graficznych, będąc implementacją koncepcji GPGPU. DirectCompute został pierwotnie opublikowany jako część DirectX 11, ale później został również udostępniony dla DirectX 10 i DirectX 10.1.

NVDIA CUDA w rosyjskim środowisku naukowym.

Od grudnia 2009 r. model programowania CUDA jest wykładana na 269 uniwersytetach na całym świecie. W Rosji kursy szkoleniowe na temat CUDA prowadzone są na uniwersytetach państwowych w Moskwie, Sankt Petersburgu, Kazaniu, Nowosybirsku i Permie, Międzynarodowym Uniwersytecie Natury Społeczeństwa i Człowieka „Dubna”, Połączonym Instytucie Badań Jądrowych, Moskiewskim Instytucie Elektroniki Technologia, Państwowy Uniwersytet Energetyczny w Iwanowie, BSTU. V.G. Shukhova, MSTU im. Bauman, RKhTU im. Mendelejew, Rosyjskie Centrum Badawcze „Instytut Kurczatowa”, Międzyregionalne Centrum Superkomputerowe Rosyjskiej Akademii Nauk, Instytut Technologiczny Taganrog (TTI SFedU).

Funkcje architektury AMD/ATI Radeon

Jest to podobne do narodzin nowych gatunków biologicznych, gdy żywe istoty ewoluują, aby poprawić swoją zdolność przystosowania się do środowiska podczas rozwoju siedlisk. Podobnie procesory graficzne, zaczynając od przyspieszonej rasteryzacji i teksturowania trójkątów, rozwinęły dodatkowe możliwości wykonywania programów cieniujących do kolorowania tych samych trójkątów. Te możliwości okazały się być poszukiwane w obliczeniach niegraficznych, gdzie w niektórych przypadkach zapewniają znaczny wzrost wydajności w porównaniu z tradycyjnymi rozwiązaniami.

Analogie czerpiemy dalej – po długiej ewolucji na lądzie ssaki przedostały się do morza, skąd wypchnęły zwykłych morskich mieszkańców. W walce konkurencyjnej ssaki wykorzystywały zarówno nowe zaawansowane zdolności, które pojawiły się na powierzchni ziemi, jak i te specjalnie nabyte w celu przystosowania się do życia w wodzie. W ten sam sposób procesory graficzne, oparte na zaletach architektury dla grafiki 3D, coraz częściej zyskują specjalną funkcjonalność przydatną do zadań innych niż grafika.

Co więc pozwala GPU na przejęcie własnego sektora w dziedzinie programów ogólnego przeznaczenia? Mikroarchitektura GPU jest zbudowana zupełnie inaczej niż konwencjonalne procesory i od samego początku ma pewne zalety. Zadania graficzne obejmują niezależne równoległe przetwarzanie danych, a procesor graficzny jest natywnie wielowątkowy. Ale ta równoległość jest dla niego tylko radością. Mikroarchitektura została zaprojektowana w celu wykorzystania dużej liczby wątków, które muszą zostać wykonane.

GPU składa się z kilkudziesięciu (30 dla Nvidia GT200, 20 dla Evergreen, 16 dla Fermi) rdzeni procesorów, które w terminologii Nvidia nazywają się Streaming Multiprocessor, a SIMD Engine w terminologii ATI. W ramach tego artykułu nazwiemy je miniprocesorami, ponieważ wykonują kilkaset wątków programu i mogą zrobić prawie wszystko, co potrafi zwykły procesor, ale wciąż nie wszystko.

Nazwy marketingowe są mylące - dla większego znaczenia wskazują liczbę modułów funkcjonalnych, które można odejmować i mnożyć: na przykład 320 "rdzeni" wektorowych (rdzenie). Te jądra bardziej przypominają ziarna. Lepiej myśleć o GPU jako procesorze wielordzeniowym z wieloma rdzeniami wykonującymi wiele wątków jednocześnie.

Każdy miniprocesor ma pamięć lokalną, 16 kB dla GT200, 32 kB dla Evergreen i 64 kB dla Fermi (zasadniczo programowalna pamięć podręczna L1). Ma podobny czas dostępu do pamięci podręcznej L1 konwencjonalnej jednostki centralnej i wykonuje podobne funkcje dostarczania danych do modułów funkcyjnych tak szybko, jak to możliwe. W architekturze Fermi część pamięci lokalnej można skonfigurować jako zwykłą pamięć podręczną. W GPU pamięć lokalna służy do szybkiej wymiany danych między wykonującymi się wątkami. Jeden ze zwykłych schematów programu GPU jest następujący: najpierw dane z pamięci globalnej GPU są ładowane do pamięci lokalnej. To jest zwykła pamięć wideo zlokalizowana (jak pamięć systemowa) oddzielnie od „własnego” procesora - w przypadku wideo jest lutowany przez kilka mikroukładów na tekstolicie karty graficznej. Następnie kilkaset wątków pracuje z tymi danymi w pamięci lokalnej i zapisuje wynik do pamięci globalnej, po czym jest przesyłany do CPU. Za napisanie instrukcji ładowania i rozładowywania danych z pamięci lokalnej odpowiada programista. W istocie jest to partycjonowanie danych [określonego zadania] do przetwarzania równoległego. GPU obsługuje również atomowe instrukcje zapisu/odczytu do pamięci, ale są one nieefektywne i zwykle są wymagane na końcowym etapie „sklejania” wyników obliczeń wszystkich miniprocesorów.

Pamięć lokalna jest wspólna dla wszystkich wątków działających w miniprocesorze, więc na przykład w terminologii Nvidii nazywa się ją nawet współdzieloną, a termin pamięć lokalna oznacza dokładnie odwrotnie, a mianowicie: pewien obszar osobisty oddzielnego wątku w globalnym pamięci, widocznej i dostępnej tylko dla niej. Ale oprócz pamięci lokalnej miniprocesor ma inny obszar pamięci, we wszystkich architekturach, około czterokrotnie większy. Jest on podzielony równo między wszystkie wykonujące się wątki, są to rejestry do przechowywania zmiennych i pośrednich wyników obliczeń. Każdy wątek ma kilkadziesiąt rejestrów. Dokładna liczba zależy od liczby wątków uruchomionych przez miniprocesor. Ta liczba jest bardzo ważna, ponieważ opóźnienie pamięci globalnej jest bardzo duże, setki cykli, a przy braku pamięci podręcznej nie ma gdzie przechowywać pośrednich wyników obliczeń.

I jeszcze jedna ważna cecha GPU: „miękka” wektoryzacja. Każdy miniprocesor ma dużą liczbę modułów obliczeniowych (8 dla GT200, 16 dla Radeona i 32 dla Fermi), ale mogą one wykonywać tylko tę samą instrukcję, z tym samym adresem programu. W tym przypadku operandy mogą być różne, różne wątki mają swoje własne. Na przykład instrukcja dodaj zawartość dwóch rejestrów: jest wykonywany jednocześnie przez wszystkie urządzenia komputerowe, ale pobierane są różne rejestry. Zakłada się, że wszystkie wątki programu GPU, realizujące równoległe przetwarzanie danych, na ogół poruszają się równolegle w kodzie programu. Dzięki temu wszystkie moduły obliczeniowe są ładowane równomiernie. A jeśli wątki, ze względu na rozgałęzienia w programie, rozeszły się w swojej ścieżce wykonania kodu, to następuje tzw. serializacja. Wtedy nie wszystkie moduły obliczeniowe są używane, ponieważ wątki podają do wykonania różne instrukcje, a blok modułów obliczeniowych może wykonać, jak już powiedzieliśmy, tylko instrukcję z jednym adresem. I oczywiście wydajność w tym samym czasie spada w stosunku do maksimum.

Zaletą jest to, że wektoryzacja jest całkowicie automatyczna, nie jest programowana przy użyciu SSE, MMX i tak dalej. A sam GPU radzi sobie z rozbieżnościami. Teoretycznie można pisać programy na GPU bez myślenia o wektorowej naturze modułów wykonawczych, ale szybkość takiego programu nie będzie bardzo duża. Minusem jest duża szerokość wektora. To więcej niż nominalna liczba modułów funkcjonalnych i wynosi 32 dla procesorów graficznych Nvidia i 64 dla Radeona. Wątki przetwarzane są w blokach o odpowiedniej wielkości. Nvidia nazywa ten blok wątków terminem warp, AMD – wave front, co oznacza to samo. Tak więc, na 16 urządzeniach obliczeniowych, „front fali” o długości 64 wątków jest przetwarzany w czterech cyklach (przy założeniu zwykłej długości instrukcji). Autor preferuje w tym przypadku termin osnowa, ze względu na skojarzenie z terminem żeglarskim osnowy, oznaczającym linę zawiązaną ze skręconych lin. Tak więc nici „skręcają się” i tworzą integralną wiązkę. Jednak „czoło fali” może być również kojarzone z morzem: instrukcje docierają do siłowników w taki sam sposób, jak fale toczą się jedna po drugiej na brzeg.

Jeśli wszystkie wątki postępują jednakowo w wykonywaniu programu (są w tym samym miejscu) i w ten sposób wykonują tę samą instrukcję, wszystko jest w porządku, ale jeśli nie, zwalnia. W tym przypadku wątki z tego samego osnowy lub czoła fali znajdują się w różnych miejscach w programie, są podzielone na grupy wątków, które mają tę samą wartość numeru instrukcji (innymi słowy, wskaźnik instrukcji). I tak jak poprzednio, tylko wątki jednej grupy są wykonywane jednocześnie - wszystkie wykonują tę samą instrukcję, ale z różnymi argumentami. W rezultacie wypaczenie jest wykonywane wielokrotnie wolniej, na ile grup jest podzielony, a liczba wątków w grupie nie ma znaczenia. Nawet jeśli grupa składa się tylko z jednego wątku, to i tak będzie trwać tak długo, jak pełne wypaczenie. Sprzętowo jest to realizowane przez maskowanie pewnych wątków, to znaczy instrukcje są formalnie wykonywane, ale wyniki ich wykonania nie są nigdzie rejestrowane i nie są wykorzystywane w przyszłości.

Chociaż każdy miniprocesor (Streaming MultiProcessor lub SIMD Engine) wykonuje instrukcje należące tylko do jednego warp (pęczki wątków) w danym momencie, ma kilkadziesiąt aktywnych wypaczeń w puli wykonywalnej. Po wykonaniu instrukcji jednego osnowy, miniprocesor wykonuje nie następną z kolei instrukcję wątków tego osnowy, ale instrukcje kogoś innego w osnowie. To wypaczenie może znajdować się w zupełnie innym miejscu w programie, nie wpłynie to na szybkość, ponieważ tylko wewnątrz wypaczenia instrukcje wszystkich wątków muszą być takie same, aby były wykonywane z pełną prędkością.

W tym przypadku każdy z 20 silników SIMD ma cztery aktywne fronty fal, każdy z 64 wątkami. Każdy wątek jest oznaczony krótką linią. Razem: 64 × 4 × 20 = 5120 wątków

Tak więc, biorąc pod uwagę, że każdy front osnowy lub fali składa się z 32-64 wątków, miniprocesor ma kilkaset aktywnych wątków, które są wykonywane prawie jednocześnie. Poniżej zobaczymy, jakie korzyści architektoniczne obiecuje tak duża liczba równoległych wątków, ale najpierw zastanowimy się, jakie ograniczenia mają miniprocesory tworzące GPU.

Najważniejsze jest to, że GPU nie ma stosu, na którym można przechowywać parametry funkcji i zmienne lokalne. Ze względu na dużą liczbę wątków na stos po prostu nie ma miejsca na chipie. Rzeczywiście, ponieważ procesor GPU jednocześnie wykonuje około 10 000 wątków, przy rozmiarze stosu pojedynczego wątku wynoszącym 100 KB, całkowita ilość wyniesie 1 GB, co odpowiada standardowej ilości całej pamięci wideo. Co więcej, nie ma możliwości umieszczenia stosu o znaczącej wielkości w samym rdzeniu GPU. Na przykład, jeśli umieścisz 1000 bajtów stosu na wątek, to tylko jeden miniprocesor będzie wymagał 1 MB pamięci, co stanowi prawie pięciokrotność całkowitej ilości pamięci lokalnej miniprocesora i pamięci przeznaczonej na przechowywanie rejestrów.

Dlatego w programie GPU nie ma rekurencji i nie można tak naprawdę odwrócić się za pomocą wywołań funkcji. Wszystkie funkcje są bezpośrednio podstawiane do kodu podczas kompilacji programu. Ogranicza to zakres GPU do zadań obliczeniowych. Czasami możliwe jest użycie ograniczonej emulacji stosu przy użyciu pamięci globalnej dla algorytmów rekurencji o znanej małej głębokości iteracji, ale nie jest to typowa aplikacja GPU. Aby to zrobić, konieczne jest specjalne opracowanie algorytmu, aby zbadać możliwość jego implementacji bez gwarancji udanego przyspieszenia w porównaniu z procesorem.

Fermi po raz pierwszy wprowadził możliwość korzystania z funkcji wirtualnych, ale ponownie ich użycie jest ograniczone brakiem dużej, szybkiej pamięci podręcznej dla każdego wątku. 1536 wątków odpowiada 48 KB lub 16 KB L1, czyli funkcje wirtualne w programie mogą być używane stosunkowo rzadko, w przeciwnym razie stos będzie również używał wolnej pamięci globalnej, co spowolni wykonywanie i najprawdopodobniej nie przyniesie korzyści w porównaniu do wersji procesora.

Tak więc GPU jest przedstawiany jako koprocesor obliczeniowy, do którego dane są ładowane, przetwarzane przez jakiś algorytm i wytwarzany jest wynik.

Korzyści z architektury

Ale uważa GPU za bardzo szybki. Pomaga mu w tym wysoka wielowątkowość. Duża liczba aktywnych wątków umożliwia częściowe ukrycie dużego opóźnienia osobno zlokalizowanej globalnej pamięci wideo, która wynosi około 500 cykli. Wyrównuje się szczególnie dobrze w przypadku kodu o dużej gęstości. działania arytmetyczne. W ten sposób hierarchia pamięci podręcznej L1-L2-L3 kosztująca tranzystory nie jest wymagana. Zamiast tego na chipie można umieścić wiele modułów obliczeniowych, zapewniając wyjątkową wydajność arytmetyczną. W międzyczasie instrukcje jednego wątku lub wypaczenia są wykonywane, inne setki wątków spokojnie czekają na swoje dane.

Fermi wprowadził pamięć podręczną drugiego poziomu o wielkości około 1 MB, ale nie można jej porównać z pamięciami podręcznymi nowoczesnych procesorów, jest bardziej przeznaczona do komunikacji między rdzeniami i różnymi sztuczkami programowymi. Jeśli jego rozmiar zostanie podzielony na wszystkie dziesiątki tysięcy wątków, każdy będzie miał bardzo znikomą ilość.

Ale oprócz opóźnień pamięci globalnej, istnieje wiele innych opóźnień w urządzeniu obliczeniowym, które należy ukryć. Jest to opóźnienie transferu danych w chipie z urządzeń obliczeniowych do pamięci podręcznej pierwszego poziomu, czyli lokalnej pamięci GPU, oraz do rejestrów i pamięci podręcznej instrukcji. Plik rejestru, podobnie jak pamięć lokalna, znajdują się oddzielnie od modułów funkcjonalnych, a szybkość dostępu do nich wynosi kilkanaście cykli. I znowu, duża liczba wątków, aktywnych wypaczeń, może skutecznie ukryć to opóźnienie. Co więcej, całkowita przepustowość (przepustowość) dostępu do pamięci lokalnej całego GPU, biorąc pod uwagę liczbę składających się na nią miniprocesorów, jest znacznie większa niż przepustowość dostępu do pamięci podręcznej pierwszego poziomu w nowoczesnych procesorach. GPU może przetwarzać znacznie więcej danych w jednostce czasu.

Możemy od razu powiedzieć, że jeśli GPU nie zostanie wyposażony w dużą liczbę równoległych wątków, to będzie miał prawie zerową wydajność, bo będzie pracował w tym samym tempie, jakby był w pełni obciążony, a pracy będzie wykonywał znacznie mniej. Na przykład niech pozostanie tylko jeden wątek zamiast 10 000: wydajność spadnie około tysiąca razy, ponieważ nie tylko nie wszystkie bloki zostaną załadowane, ale wszystkie opóźnienia również będą miały wpływ.

Problem ukrywania opóźnień jest również dotkliwy w przypadku nowoczesnych procesorów o wysokiej częstotliwości; do jego eliminacji stosuje się wyrafinowane metody - głębokie potokowanie, wykonywanie instrukcji w kolejności (nie w kolejności). Wymaga to złożonych harmonogramów wykonywania instrukcji, różnych buforów itp., które zajmują miejsce na chipie. To wszystko jest wymagane do uzyskania najlepszej wydajności w trybie jednowątkowym.

Ale w przypadku GPU wszystko to nie jest konieczne, jest architektonicznie szybsze w przypadku zadań obliczeniowych z dużą liczbą wątków. Zamiast tego przekształca wielowątkowość w wydajność, tak jak kamień filozoficzny zamienia ołów w złoto.

GPU został pierwotnie zaprojektowany do optymalnego wykonywania programów cieniujących dla pikseli trójkątów, które są oczywiście niezależne i mogą być wykonywane równolegle. I z tego stanu ewoluowała, dodając różne funkcje (pamięć lokalna i adresowalny dostęp do pamięci wideo, a także komplikując zestaw instrukcji) w bardzo wydajne urządzenie obliczeniowe, które nadal może być skutecznie stosowane tylko w przypadku algorytmów pozwalających na wysoce równoległą implementację przy użyciu ograniczonej ilości pamięci lokalnej.

Przykład

Jednym z najbardziej klasycznych problemów GPU jest problem obliczania interakcji N ciał tworzących pole grawitacyjne. Ale jeśli na przykład musimy obliczyć ewolucję układu Ziemia-Księżyc-Słońce, to GPU jest dla nas złym pomocnikiem: obiektów jest niewiele. Dla każdego obiektu konieczne jest obliczenie interakcji ze wszystkimi innymi obiektami, a są tylko dwa z nich. W przypadku ruchu Układu Słonecznego ze wszystkimi planetami i ich księżycami (około kilkuset obiektów) GPU nadal nie jest zbyt wydajny. Jednak procesor wielordzeniowy, ze względu na wysokie koszty ogólne zarządzania wątkami, również nie będzie w stanie pokazać całej swojej mocy, będzie działał w trybie jednowątkowym. Ale jeśli musisz również obliczyć trajektorie komet i obiektów pasa asteroid, to już jest to zadanie dla GPU, ponieważ jest wystarczająco dużo obiektów, aby utworzyć wymaganą liczbę równoległych wątków obliczeniowych.

GPU będzie również działać dobrze, jeśli konieczne będzie obliczenie kolizji gromad kulistych setek tysięcy gwiazd.

Kolejna okazja do wykorzystania mocy GPU w problemie N-body pojawia się, gdy trzeba obliczyć wiele indywidualnych problemów, aczkolwiek z niewielką liczbą ciał. Na przykład, jeśli chcesz obliczyć ewolucję jednego systemu dla różnych opcji prędkości początkowych. Wtedy możliwe będzie efektywne wykorzystanie GPU bez problemów.

Szczegóły mikroarchitektury AMD Radeon

Rozważyliśmy podstawowe zasady organizacji GPU, są one wspólne dla akceleratorów wideo wszystkich producentów, ponieważ początkowo mieli jedno zadanie docelowe - programy cieniujące. Jednak producenci stwierdzili, że można się nie zgodzić co do szczegółów implementacji mikroarchitektonicznej. Chociaż procesory różnych dostawców są czasami bardzo różne, nawet jeśli są kompatybilne, takie jak Pentium 4 i Athlon lub Core. Architektura Nvidii jest już powszechnie znana, teraz przyjrzymy się Radeonowi i podkreślimy główne różnice w podejściu tych dostawców.

Karty graficzne AMD otrzymały pełne wsparcie dla komputerów ogólnego przeznaczenia od czasu rodziny Evergreen, która również była pionierem specyfikacji DirectX 11. Karty z rodziny 47xx mają szereg istotnych ograniczeń, które zostaną omówione poniżej.

Różnice w wielkości pamięci lokalnej (32 KB dla Radeona w porównaniu z 16 KB dla GT200 i 64 KB dla Fermi) na ogół nie są fundamentalne. Jak również rozmiar czoła fali 64 wątków dla AMD w porównaniu do 32 wątków na osnowę dla Nvidii. Prawie każdy program GPU można łatwo przekonfigurować i dostroić do tych parametrów. Wydajność może się zmieniać o kilkadziesiąt procent, ale w przypadku GPU nie jest to tak ważne, ponieważ program GPU zwykle działa dziesięciokrotnie wolniej niż jego odpowiednik dla CPU lub dziesięciokrotnie szybciej, albo w ogóle nie działa.

Ważniejsze jest użycie Technologie AMD VLIW (bardzo długie słowo instrukcji). Nvidia używa skalarnych prostych instrukcji operujących na rejestrach skalarnych. Jego akceleratory implementują prosty klasyczny RISC. Karty graficzne AMD mają taką samą liczbę rejestrów jak GT200, ale rejestry są wektorem 128-bitowym. Każda instrukcja VLIW działa na kilku czteroskładnikowych rejestrach 32-bitowych, co jest podobne do SSE, ale możliwości VLIW są ​​znacznie szersze. To nie jest SIMD (Single Instruction Multiple Data) jak SSE - tutaj instrukcje dla każdej pary argumentów mogą być różne, a nawet zależne! Na przykład, niech składniki rejestru A będą nazwane a1, a2, a3, a4; dla rejestru B - podobnie. Można obliczyć za pomocą pojedynczej instrukcji, która wykonuje się w jednym cyklu, na przykład liczba a1×b1+a2×b2+a3×b3+a4×b4 lub wektor dwuwymiarowy (a1×b1+a2×b2, a3× b3+a4×b4 ).

Było to możliwe dzięki niższej częstotliwości GPU niż CPU i silnemu ograniczeniu procesów technicznych w ostatnie lata. Nie wymaga to żadnego harmonogramu, prawie wszystko jest wykonywane na zegar.

Dzięki instrukcjom wektorowym szczytowa wydajność Radeona z pojedynczą precyzją jest bardzo wysoka, rzędu teraflopów.

Jeden rejestr wektorowy może przechowywać jedną liczbę o podwójnej precyzji zamiast czterech liczb o pojedynczej precyzji. A jedna instrukcja VLIW może albo dodać dwie pary podwójnych, albo pomnożyć dwie liczby, albo pomnożyć dwie liczby i dodać do trzeciej. Tak więc szczytowa wydajność w trybie double jest około pięć razy niższa niż w modelu float. W przypadku starszych modeli Radeon odpowiada to Wydajność Nvidii Tesla na nowej architekturze Fermi i znacznie wyższa wydajność niż podwójne karty na architekturze GT200. W konsumenckich kartach graficznych Geforce opartych na Fermi maksymalna prędkość podwójnych obliczeń została zmniejszona czterokrotnie.


Schemat ideowy pracy Radeona. Pokazano tylko jeden miniprocesor z 20 działających równolegle

Producenci GPU, w przeciwieństwie do producentów procesorów (przede wszystkim kompatybilnych z x86), nie są związani problemami ze zgodnością. Program GPU jest najpierw kompilowany do jakiegoś kodu pośredniego, a kiedy program jest uruchamiany, sterownik kompiluje ten kod w instrukcje maszynowe specyficzne dla konkretny model. Jak opisano powyżej, producenci procesorów graficznych wykorzystali to, wymyślając wygodną architekturę zestawu instrukcji ISA (Instruction Set Architecture) dla swoich procesorów graficznych i zmieniając je z pokolenia na pokolenie. W każdym razie dodało to pewien procent wydajności ze względu na brak (jako zbędny) dekodera. Ale AMD poszło jeszcze dalej, wymyślając własny format do rozmieszczania instrukcji w kodzie maszynowym. Nie są one ułożone sekwencyjnie (zgodnie z listą programów), ale w sekcjach.

Najpierw jest sekcja instrukcji skoku warunkowego, które mają łącza do sekcji ciągłych instrukcji arytmetycznych odpowiadających różnym gałęziom gałęzi. Nazywane są wiązkami VLIW (pakiety instrukcji VLIW). Sekcje te zawierają tylko instrukcje arytmetyczne z danymi z rejestrów lub pamięci lokalnej. Taka organizacja upraszcza przepływ instrukcji i ich dostarczanie do jednostek wykonawczych. Jest to tym bardziej przydatne, że instrukcje VLIW są ​​stosunkowo duże. Istnieją również sekcje dotyczące instrukcji dostępu do pamięci.

Sekcje warunkowej instrukcji oddziału
Sekcja 0Rozgałęzienie 0Link do sekcji #3 ciągłych instrukcji arytmetycznych
Sekcja 1Rozgałęzienie 1Link do sekcji #4
Sekcja 2Rozgałęzienie 2Link do sekcji #5
Sekcje ciągłych instrukcji arytmetycznych
Sekcja 3Instrukcja VLIW 0Instrukcja VLIW 1Instrukcja VLIW 2Instrukcja VLIW 3
Sekcja 4Instrukcja VLIW 4Instrukcja VLIW 5
Sekcja 5Instrukcja VLIW 6Instrukcja VLIW 7Instrukcja VLIW 8Instrukcja VLIW 9

Karty graficzne obu producentów (zarówno Nvidia, jak i AMD) mają również wbudowane instrukcje do szybkiego obliczania podstawowych funkcji matematycznych, pierwiastka kwadratowego, wykładnika, logarytmów, sinusów i cosinusów dla liczb o pojedynczej precyzji w kilku cyklach. Są do tego specjalne bloki obliczeniowe. „Przyszły” z potrzeby zaimplementowania szybkiego aproksymacji tych funkcji w shaderach geometrii.

Nawet jeśli ktoś nie wiedział, że GPU służą do grafiki, a zapoznał się jedynie z charakterystyką techniczną, to po tym znaku mógł się domyślić, że te koprocesory obliczeniowe wywodzą się z akceleratorów wideo. Podobnie niektóre cechy ssaków morskich doprowadziły naukowców do przekonania, że ​​ich przodkowie byli stworzeniami lądowymi.

Ale bardziej oczywistą cechą, która zdradza pochodzenie graficzne urządzenia, są klocki do odczytywania tekstur dwuwymiarowych i trójwymiarowych z obsługą interpolacji dwuliniowej. Są szeroko stosowane w programach GPU, ponieważ zapewniają szybszy i łatwiejszy odczyt tablic danych tylko do odczytu. Jeden z standardowe opcje Zachowanie aplikacji GPU polega na odczytywaniu macierzy danych początkowych, przetwarzaniu ich w rdzeniach obliczeniowych i zapisywaniu wyniku do innej macierzy, która jest następnie przesyłana z powrotem do procesora. Taki schemat jest standardowy i powszechny, ponieważ jest wygodny dla architektury GPU. Zadania wymagające intensywnych odczytów i zapisów do jednego dużego obszaru pamięci globalnej, a więc zawierającej zależności danych, są trudne do zrównoleglenia i sprawnej realizacji na GPU. Ponadto ich wydajność będzie w dużym stopniu zależeć od opóźnienia pamięci globalnej, która jest bardzo duża. Ale jeśli zadanie jest opisane przez wzorzec „odczyt danych – przetwarzanie – zapisanie wyniku”, to prawie na pewno można uzyskać duży impuls dzięki jego wykonaniu na GPU.

W przypadku danych tekstur w GPU istnieje osobna hierarchia małych pamięci podręcznych pierwszego i drugiego poziomu. Zapewnia również przyspieszenie dzięki wykorzystaniu tekstur. Ta hierarchia pierwotnie pojawiła się w procesorach graficznych, aby wykorzystać lokalizację dostępu do tekstur: oczywiście po przetworzeniu jednego piksela sąsiedni piksel (z dużym prawdopodobieństwem) będzie wymagał ściśle rozmieszczonych danych tekstur. Jednak wiele algorytmów dla konwencjonalnych obliczeń ma podobny charakter dostępu do danych. Tak więc pamięci podręczne tekstur z grafiki będą bardzo przydatne.

Chociaż rozmiar pamięci podręcznych L1-L2 w kartach Nvidia i AMD jest w przybliżeniu taki sam, co jest oczywiście spowodowane wymaganiami dotyczącymi optymalności pod względem grafiki w grach, opóźnienie dostępu do tych pamięci podręcznych znacznie się różni. Opóźnienia dostępu Nvidii są wyższe, a pamięci podręczne tekstur w Geforce pomagają przede wszystkim zmniejszyć obciążenie magistrali pamięci, a nie bezpośrednio przyspieszać dostęp do danych. Nie jest to zauważalne w programach graficznych, ale jest ważne dla programów ogólnego przeznaczenia. W Radeonie opóźnienie pamięci podręcznej tekstur jest niższe, ale opóźnienie pamięci lokalnej miniprocesorów jest wyższe. Oto przykład: dla optymalnego mnożenia macierzy na kartach Nvidii lepiej użyć pamięci lokalnej, ładując tam macierz blok po bloku, a dla AMD lepiej polegać na pamięci podręcznej tekstur o niskim opóźnieniu, odczytując elementy matrycy jak potrzebne. Ale jest to już dość subtelna optymalizacja i dla algorytmu, który został już zasadniczo przeniesiony na GPU.

Ta różnica pojawia się również podczas korzystania z tekstur 3D. Jeden z pierwszych benchmarków obliczeniowych GPU, który wykazał poważną przewagę dla AMD, po prostu wykorzystywał tekstury 3D, ponieważ działał z trójwymiarową macierzą danych. Opóźnienie dostępu do tekstur w Radeonie jest znacznie szybsze, a obudowa 3D jest dodatkowo bardziej zoptymalizowana sprzętowo.

Aby uzyskać maksymalną wydajność sprzętu różnych firm, konieczne jest dostrojenie aplikacji pod konkretną kartę, ale jest to o rząd wielkości mniej istotne niż w zasadzie opracowanie algorytmu dla architektury GPU.

Ograniczenia serii Radeon 47xx

W tej rodzinie obsługa obliczeń GPU jest niepełna. Są trzy ważne chwile. Po pierwsze, nie ma pamięci lokalnej, to znaczy jest fizycznie, ale nie ma uniwersalnego dostępu wymaganego przez współczesny standard programów GPU. Jest programowo emulowany w pamięci globalnej, co oznacza, że ​​nie będzie korzystać z niego w przeciwieństwie do w pełni funkcjonalnego procesora graficznego. Drugi punkt to ograniczone wsparcie dla różnych instrukcji operacji na pamięci atomowej i instrukcji synchronizacji. A trzeci punkt jest dość mały rozmiar pamięć podręczna instrukcji: począwszy od określonego rozmiaru programu, prędkość spada kilka razy. Istnieją również inne drobne ograniczenia. Można powiedzieć, że na tej karcie graficznej dobrze sprawdzą się tylko programy, które są idealnie dopasowane do GPU. Chociaż karta graficzna może wykazywać dobre wyniki w Gigaflopsach w prostych programach testowych, które działają tylko z rejestrami, problematyczne jest skuteczne zaprogramowanie dla niej czegoś złożonego.

Zalety i wady Evergreen

Jeśli porównamy produkty AMD i Nvidii, to pod względem obliczeń GPU seria 5xxx wygląda jak bardzo potężny GT200. Tak potężny, że pod względem wydajności przewyższa Fermiego około dwa i pół raza. Zwłaszcza po obcięciu parametrów nowych kart graficznych Nvidii zmniejszono liczbę rdzeni. Jednak pojawienie się pamięci podręcznej L2 w Fermi upraszcza implementację niektórych algorytmów na GPU, rozszerzając w ten sposób zakres GPU. Co ciekawe, w przypadku dobrze zoptymalizowanych pod kątem poprzedniej generacji programów GT200 CUDA innowacje architektoniczne Fermi często nic nie robiły. Przyspieszyły proporcjonalnie do wzrostu liczby modułów obliczeniowych, czyli mniej niż dwukrotnie (dla liczb o pojedynczej precyzji), a nawet mniej, ponieważ przepustowość pamięci nie wzrosła (lub z innych powodów).

A w zadaniach, które dobrze pasują do architektury GPU i mają wyraźną wektorową naturę (na przykład mnożenie macierzy), Radeon pokazuje wydajność stosunkowo zbliżoną do teoretycznego szczytu i wyprzedza Fermiego. Nie wspominając o wielordzeniowych procesorach. Zwłaszcza w przypadku problemów z liczbami o pojedynczej precyzji.

Ale Radeon ma mniejszą powierzchnię matrycy, mniejsze rozpraszanie ciepła, zużycie energii, wyższą wydajność i odpowiednio niższy koszt. I bezpośrednio w problemach grafiki 3D zysk Fermiego, jeśli w ogóle, jest znacznie mniejszy niż różnica w obszarze kryształu. Wynika to w dużej mierze z faktu, że architektura obliczeniowa Radeona, z 16 jednostkami obliczeniowymi na miniprocesor, 64-wątkowym frontem fali i instrukcjami wektorowymi VLIW, jest idealna do swojego głównego zadania - obliczania shaderów grafiki. Dla zdecydowanej większości zwykli użytkownicy wydajność i cena w grach są priorytetem.

Z punktu widzenia profesjonalnych, naukowych programów architektura Radeon zapewnia najlepszy stosunek ceny do wydajności, wydajność na wat oraz absolutną wydajność w zadaniach, które w zasadzie dobrze pasują do architektury GPU, pozwalają na zrównoleglenie i wektoryzację.

Na przykład, w całkowicie równoległym, łatwo wektoryzowalnym problemie z wyborem klawiszy, Radeon jest kilka razy szybszy niż Geforce i kilkadziesiąt razy szybszy niż CPU.

Odpowiada to ogólnej koncepcji AMD Fusion, zgodnie z którą procesory graficzne powinny uzupełniać procesor, a w przyszłości być zintegrowane z samym rdzeniem procesora, podobnie jak koprocesor matematyczny został wcześniej przeniesiony z oddzielnego chipa do rdzenia procesora (stało się to około dwadzieścia lat temu, przed pojawieniem się pierwszych procesorów Pentium). GPU zostanie zintegrowany rdzeń graficzny oraz koprocesor wektorowy do zadań strumieniowych.

Radeon używa skomplikowanej techniki mieszania instrukcji z różnych frontów fal, gdy są wykonywane przez moduły funkcyjne. Jest to łatwe, ponieważ instrukcje są całkowicie niezależne. Zasada jest podobna do potokowego wykonywania niezależnych instrukcji przez nowoczesne procesory. Najwyraźniej umożliwia to wydajne wykonywanie złożonych, wielobajtowych, wektorowych instrukcji VLIW. Na procesorze wymaga to wyrafinowanego programu planującego, który identyfikuje niezależne instrukcje, lub użycia technologii Hyper-Threading, która również dostarcza procesorowi znane niezależne instrukcje z różnych wątków.

miara 0takt 1miara 2miara 3takt 4takt 5takt 6takt 7Moduł VLIW
fala przód 0fala przód 1fala przód 0fala przód 1fala przód 0fala przód 1fala przód 0fala przód 1
instr. 0instr. 0instr. 16instr. 16instr. 32instr. 32instr. 48instr. 48VLIW0
instr. jedenVLIW1
instr. 2VLIW2
instr. 3VLIW3
instr. czteryVLIW4
instr. 5VLIW5
instr. 6VLIW6
instr. 7VLIW7
instr. osiemVLIW8
instr. 9VLIW9
instr. dziesięćVLIW10
instr. jedenaścieVLIW11
instr. 12VLIW12
instr. 13VLIW13
instr. czternaścieVLIW14
instr. piętnaścieVLIW15

128 instrukcji o dwóch frontach falowych, z których każdy składa się z 64 operacji, wykonywanych jest przez 16 modułów VLIW w ośmiu cyklach. Występuje naprzemiennie, a każdy moduł faktycznie ma dwa cykle na wykonanie całej instrukcji, pod warunkiem, że w drugim cyklu rozpocznie równolegle wykonywanie nowej. Prawdopodobnie pomaga to w szybkim wykonaniu instrukcji VLIW, takiej jak a1×a2+b1×b2+c1×c2+d1×d2, czyli wykonanie ośmiu takich instrukcji w ośmiu cyklach. (Okazuje się, że formalnie jeden na zegar.)

Nvidia najwyraźniej nie ma tej technologii. A w przypadku braku VLIW, wysoka wydajność przy użyciu instrukcji skalarnych wymaga wysokiej częstotliwości działania, co automatycznie zwiększa rozpraszanie ciepła i stawia wysokie wymagania procesowi (aby zmusić obwód do pracy z wyższą częstotliwością).

Wadą Radeona pod względem obliczeń na GPU jest duża niechęć do rozgałęzień. GPU generalnie nie faworyzują rozgałęzień ze względu na powyższą technologię wykonywania instrukcji: natychmiast przez grupę wątków z jednym adresem programu. (Nawiasem mówiąc, ta technika nazywa się SIMT: Single Instruction - Multiple Threads (jedna instrukcja - wiele wątków), analogicznie do SIMD, gdzie jedna instrukcja wykonuje jedną operację z różnymi danymi.) . Oczywiste jest, że jeśli program nie jest całkowicie wektorowy, to im większy rozmiar czoła osnowy lub fali, tym gorzej, ponieważ gdy ścieżka wzdłuż programu się rozchodzi, powstają sąsiednie wątki więcej grup, który musi być wykonywany sekwencyjnie (serializowany). Powiedzmy, że wszystkie wątki się rozproszyły, wtedy w przypadku rozmiaru wypaczenia 32 wątków program będzie działał 32 razy wolniej. A w przypadku rozmiaru 64, podobnie jak w Radeonie, jest 64 razy wolniejszy.

To zauważalny, ale nie jedyny przejaw „niechęci”. W kartach graficznych Nvidii każdy moduł funkcjonalny, inaczej zwany rdzeniem CUDA, ma specjalną jednostkę przetwarzania gałęzi. A w kartach graficznych Radeon dla 16 modułów obliczeniowych są tylko dwie rozgałęzione jednostki sterujące (pochodzą z dziedziny jednostek arytmetycznych). Tak więc nawet proste przetwarzanie instrukcji rozgałęzienia warunkowego, nawet jeśli jego wynik jest taki sam dla wszystkich wątków na froncie fali, zajmuje dodatkowy czas. A prędkość spada.

AMD produkuje również procesory. Uważają, że w przypadku programów z wieloma gałęziami procesor jest nadal lepiej przystosowany, a GPU jest przeznaczone do programów czysto wektorowych.

Tak więc Radeon zapewnia ogólnie mniej wydajne programowanie, ale w wielu przypadkach lepszy stosunek ceny do wydajności. Innymi słowy, jest mniej programów, które można sprawnie (korzystnie) przenieść z CPU do Radeona niż programów, które można efektywnie uruchomić na Fermi. Ale z drugiej strony te, które można skutecznie przenieść, będą działały wydajniej na Radeonie na wiele sposobów.

API do obliczeń na GPU

sobie Specyfikacja techniczna Radeon wygląda atrakcyjnie, nawet jeśli nie trzeba idealizować i absolutyzować obliczeń GPU. Ale nie mniej ważne dla wydajności jest oprogramowanie niezbędne do opracowania i wykonania programu GPU - kompilatory z języka wysokiego poziomu i środowiska wykonawczego, czyli sterownik, który współdziała między częścią programu uruchomioną na CPU a GPU samo. Jest to nawet ważniejsze niż w przypadku CPU: CPU nie potrzebuje sterownika do zarządzania transferem danych, a z punktu widzenia kompilatora GPU jest bardziej wybredny. Na przykład kompilator musi zadowolić się minimalną liczbą rejestrów, aby przechowywać pośrednie wyniki obliczeń, a także zgrabnie wbudowane wywołania funkcji, ponownie używając minimalnej liczby rejestrów. W końcu im mniej rejestrów używa wątek, tym więcej wątków można uruchomić i im pełniejsze obciążenie GPU, tym lepsze ukrywanie czasu dostępu do pamięci.

I tak wsparcie programowe dla produktów Radeon wciąż pozostaje w tyle za rozwojem sprzętu. (W przeciwieństwie do sytuacji z Nvidią, gdzie wydanie sprzętu było opóźnione, a produkt został wydany w uproszczonej formie.) Niedawno kompilator OpenCL AMD był w fazie beta, z wieloma wadami. Zbyt często generował błędny kod lub odmawiał kompilacji kodu z poprawnego kodu źródłowego, albo sam podawał błąd i zawieszał się. Dopiero pod koniec wiosny pojawiła się wersja o wysokiej wydajności. Nie jest też pozbawiony błędów, ale jest ich znacznie mniej i z reguły pojawiają się na uboczu przy próbie zaprogramowania czegoś na granicy poprawności. Na przykład pracują z typem uchar4, który określa 4-bajtową zmienną czteroskładnikową. Ten typ jest w specyfikacji OpenCL, ale nie warto z nim pracować na Radeonie, ponieważ rejestry są 128-bitowe: te same cztery komponenty, ale 32-bitowe. A taka zmienna uchar4 nadal zajmie cały rejestr, potrzebne będą tylko dodatkowe operacje pakowania i dostępu do poszczególnych składowych bajtowych. Kompilator nie powinien mieć żadnych błędów, ale nie ma kompilatorów bez błędów. Nawet Intel Compiler po 11 wersjach ma błędy kompilacji. Zidentyfikowane błędy zostaną naprawione w następnej wersji, która zostanie wydana bliżej jesieni.

Ale wciąż jest wiele rzeczy, które należy poprawić. Na przykład, do tej pory standardowy sterownik GPU dla Radeona nie wspierał obliczeń GPU przy użyciu OpenCL. Użytkownik musi pobrać i zainstalować dodatkowy specjalny pakiet.

Ale najważniejszy jest brak jakichkolwiek bibliotek funkcji. W przypadku liczb rzeczywistych o podwójnej precyzji nie ma nawet sinusa, cosinusa i wykładnika. Cóż, nie jest to wymagane do dodawania/mnożenia macierzy, ale jeśli chcesz zaprogramować coś bardziej złożonego, musisz napisać wszystkie funkcje od zera. Lub poczekaj na nową wersję SDK. ACML zostanie wkrótce wydany ( Rdzeń AMD Math Library) dla rodziny procesorów graficznych Evergreen z obsługą podstawowych funkcji macierzy.

Na chwilę obecną, zdaniem autora artykułu, wykorzystanie API Direct Compute 5.0 wydaje się realne do programowania kart graficznych Radeon, oczywiście biorąc pod uwagę ograniczenia: orientację na platformę Windows 7 oraz Windows Vista. Microsoft ma duże doświadczenie w tworzeniu kompilatorów i wkrótce możemy spodziewać się w pełni funkcjonalnego wydania, Microsoft jest tym bezpośrednio zainteresowany. Ale Direct Compute koncentruje się na potrzebach aplikacji interaktywnych: aby coś obliczyć i natychmiast zwizualizować wynik - na przykład przepływ cieczy po powierzchni. Nie oznacza to, że nie można go używać po prostu do obliczeń, ale nie jest to jego naturalny cel. Na przykład Microsoft nie planuje dodawać funkcji bibliotecznych do Direct Compute – dokładnie tych, których AMD w tej chwili nie ma. Oznacza to, że to, co można teraz efektywnie obliczyć na Radeonie - niektóre niezbyt skomplikowane programy - można również zaimplementować na Direct Compute, który jest znacznie prostszy niż OpenCL i powinien być bardziej stabilny. Co więcej, jest całkowicie przenośny i będzie działał zarówno na Nvidii, jak i AMD, więc będziesz musiał skompilować program tylko raz, podczas gdy implementacje OpenCL SDK Nvidii i AMD nie są dokładnie kompatybilne. (W tym sensie, że jeśli opracujesz program OpenCL w systemie AMD przy użyciu SDK AMD OpenCL, może nie działać tak łatwo na Nvidii. Być może będziesz musiał skompilować ten sam tekst za pomocą SDK Nvidii. I na odwrót, oczywiście. )

Ponadto w OpenCL istnieje wiele nadmiarowych funkcji, ponieważ OpenCL ma być uniwersalnym językiem programowania i interfejsem API dla szerokiej gamy systemów. Oraz GPU, CPU i Cell. Jeśli więc chcesz napisać program dla typowego systemu użytkownika (procesor plus karta graficzna), OpenCL nie wydaje się być, że tak powiem, „wysoce produktywny”. Każda funkcja ma dziesięć parametrów, a dziewięć z nich musi mieć wartość 0. Aby ustawić każdy parametr, musisz wywołać specjalną funkcję, która również ma parametry.

A najważniejszą obecną zaletą Direct Compute jest to, że użytkownik nie musi instalować specjalnego pakietu: wszystko, co jest potrzebne, jest już w DirectX 11.

Problemy rozwoju obliczeń GPU

Jeśli zajmiemy się komputerami osobistymi, sytuacja wygląda następująco: nie ma wielu zadań, które wymagają dużej mocy obliczeniowej i których bardzo brakuje konwencjonalnemu dwurdzeniowemu procesorowi. Wyglądało to tak, jakby wielkie, żarłoczne, ale niezdarne potwory wypełzły z morza na ląd, a na lądzie prawie nie było nic do jedzenia. A pierwotne siedliska na powierzchni Ziemi zmniejszają się, ucząc się zużywać mniej, jak to zwykle bywa, gdy brakuje zasobów naturalnych. Gdyby dzisiaj istniała taka sama potrzeba wydajności, jak 10-15 lat temu, obliczenia na GPU zostałyby zaakceptowane z hukiem. I tak na pierwszy plan wysuwają się problemy kompatybilności i względnej złożoności programowania GPU. Lepiej jest napisać program, który działa na wszystkich systemach, niż program, który jest szybki, ale działa tylko na GPU.

Perspektywy dla procesorów graficznych są nieco lepsze pod względem wykorzystania w zastosowaniach profesjonalnych i sektorze stacji roboczych, ponieważ istnieje większe zapotrzebowanie na wydajność. Pojawiają się wtyczki do edytorów 3D obsługujących GPU: na przykład do renderowania z ray tracingiem - nie mylić ze zwykłym renderowaniem GPU! Coś się pojawia również w edytorach 2D i prezentacjach, z szybszym tworzeniem złożonych efektów. Programy do przetwarzania wideo również stopniowo uzyskują wsparcie dla GPU. Powyższe zadania, ze względu na ich równoległy charakter, dobrze pasują do architektury GPU, ale teraz stworzono bardzo dużą bazę kodu, debugowano, zoptymalizowano pod kątem wszystkich możliwości procesora, więc pojawienie się dobrych implementacji GPU zajmie trochę czasu.

W tym segmencie ujawniają się również takie słabości GPU, jak ograniczona ilość pamięci wideo – około 1 GB dla konwencjonalnych GPU. Jednym z głównych czynników zmniejszających wydajność programów GPU jest konieczność wymiany danych między procesorem a procesorem graficznym za pośrednictwem wolnej magistrali, a ze względu na ograniczoną ilość pamięci należy przesłać więcej danych. I tutaj koncepcja AMD polegająca na połączeniu GPU i CPU w jednym module wygląda obiecująco: można poświęcić wysoką przepustowość pamięci graficznej na rzecz łatwego i prostego dostępu do pamięci współdzielonej, co więcej, z mniejszymi opóźnieniami. Ta wysoka przepustowość obecnej pamięci wideo DDR5 jest o wiele bardziej potrzebna bezpośrednio programy graficzne niż większość programów obliczeniowych GPU. Ogólnie, Wspólna pamięć GPU i CPU po prostu znacznie rozszerzą zakres GPU, umożliwią wykorzystanie jego możliwości obliczeniowych w małych podzadaniach programów.

A przede wszystkim procesory graficzne są poszukiwane w dziedzinie obliczeń naukowych. Zbudowano już kilka superkomputerów opartych na GPU, które wykazują bardzo wysokie wyniki w teście operacji na macierzach. Problemy naukowe są tak różnorodne i liczne, że zawsze jest zestaw, który idealnie pasuje do architektury GPU, dla której zastosowanie GPU ułatwia uzyskanie wysokiej wydajności.

Jeśli wybierzesz jedno spośród wszystkich zadań współczesnych komputerów, będzie to grafika komputerowa - obraz świata, w którym żyjemy. A optymalna do tego celu architektura nie może być zła. Jest to tak ważne i fundamentalne zadanie, że specjalnie do niego zaprojektowany sprzęt musi być uniwersalny i optymalny do różnych zadań. Co więcej, karty graficzne z powodzeniem ewoluują.

Jeden z najbardziej ukryte funkcje, w ostatniej aktualizacji systemu Windows 10, umożliwia sprawdzenie, które aplikacje używają procesora graficznego (GPU). Jeśli kiedykolwiek otwierałeś Menedżera zadań, prawdopodobnie sprawdziłeś wykorzystanie procesora, aby zobaczyć, które aplikacje najbardziej obciążają procesor. W najnowsze aktualizacje dodano podobną funkcję, ale dla GPU GPU. Pomaga zrozumieć, jak intensywne jest Twoje oprogramowanie i gry na GPU bez pobierania oprogramowania innych firm. Jest jeszcze jedna interesująca funkcja, która pomaga odciążyć procesor na GPU. Polecam poczytać jak wybrać.

Dlaczego nie mam GPU w Menedżerze zadań?

Niestety nie wszystkie karty graficzne będą w stanie dostarczyć systemowi Windows statystyki potrzebne do odczytu GPU. Dla pewności możesz szybko użyć narzędzia diagnostycznego DirectX do przetestowania tej technologii.

  1. Kliknij " Początek"a w wyszukiwaniu napisz dxdiag aby uruchomić narzędzie diagnostyczne DirectX.
  2. Przejdź do zakładki „ Ekran", bezpośrednio w kolumnie kierowcy"powinieneś mieć Model WDDM wersja większa niż 2.0, aby korzystać z wykresów GPU w menedżerze zadań.

Włącz wykres GPU w Menedżerze zadań

Aby zobaczyć użycie GPU dla każdej aplikacji, musisz otworzyć Menedżera zadań.

  • Naciśnij kombinację przycisków Ctrl + Shift + Esc aby otworzyć Menedżera zadań.
  • Kliknij kliknij prawym przyciskiem myszy kliknij w menedżerze zadań na puste pole " Nazwa" i sprawdź z menu rozwijanego GPU. Możesz również zauważyć Rdzeń GPU aby zobaczyć, które programy go używają.
  • Teraz w menedżerze zadań wykres GPU i rdzeń GPU są widoczne po prawej stronie.


Zobacz ogólną wydajność GPU

Możesz śledzić ogólne użycie GPU, aby monitorować i analizować pod dużym obciążeniem. W takim przypadku wszystko, czego potrzebujesz, możesz zobaczyć w „ Wydajność"Wybierając procesor graficzny.


Każdy element GPU jest podzielony na indywidualne wykresy, aby zapewnić jeszcze lepszy wgląd w sposób wykorzystania GPU. Jeśli chcesz zmienić wyświetlane wykresy, możesz kliknąć małą strzałkę obok tytułu każdego zadania. Ten ekran pokazuje również wersję i datę sterownika, co jest dobrą alternatywą dla korzystania z DXDiag lub Menedżera urządzeń.


Nigdy nie ma zbyt wielu rdzeni...

Nowoczesne procesory graficzne to potworne szybkie bestie zdolne do przeżuwania gigabajtów danych. Jednak człowiek jest przebiegły i bez względu na to, jak rośnie moc obliczeniowa, wymyśla zadania coraz trudniejsze, więc przychodzi moment, kiedy trzeba ze smutkiem stwierdzić, że potrzebna jest optymalizacja 🙁

W tym artykule opisano podstawowe pojęcia, aby ułatwić poruszanie się w teorii optymalizacji GPU i podstawowe zasady, tak aby te pojęcia były rzadziej używane.

Powody, dla których procesory graficzne są skuteczne w radzeniu sobie z dużymi ilościami danych, które wymagają przetwarzania:

  • mają duże możliwości równoległego wykonywania zadań (wiele, wiele procesorów)
  • wysoka przepustowość pamięci

Przepustowość pamięci- tyle informacji - bitów lub gigabajtów - można przesłać na jednostkę czasu, sekundę lub cykl procesora.

Jednym z zadań optymalizacji jest wykorzystanie maksymalnej przepustowości - w celu zwiększenia wydajności wydajność(najlepiej, aby była równa przepustowości pamięci).

Aby poprawić wykorzystanie przepustowości:

  • zwiększyć ilość informacji - w pełni wykorzystać przepustowość (np. każdy strumień działa z float4)
  • zmniejszyć opóźnienia - opóźnienie między operacjami

Czas oczekiwania- przedział czasu pomiędzy momentami, w których kontroler zażądał określonej komórki pamięci, a momentem, w którym dane stały się dostępne dla procesora w celu wykonania instrukcji. Nie możemy w żaden sposób wpływać na samo opóźnienie - te ograniczenia występują na poziomie sprzętowym. To z powodu tego opóźnienia procesor może jednocześnie obsługiwać kilka wątków - podczas gdy wątek A zażądał przydzielenia mu pamięci, wątek B może coś obliczyć, a wątek C może czekać, aż dotrą żądane dane.

Jak zmniejszyć opóźnienie, jeśli używana jest synchronizacja:

  • zmniejszyć liczbę wątków w bloku
  • zwiększyć liczbę grup bloków

Pełne wykorzystanie zasobów GPU - Zajęcie GPU

W górnolotnych rozmowach na temat optymalizacji często pojawia się określenie - zajętość GPU lub zajętość jądra- odzwierciedla efektywność wykorzystania zasobów-pojemności karty graficznej. Osobno zauważam, że nawet jeśli korzystasz ze wszystkich zasobów, nie oznacza to, że używasz ich poprawnie.

Moc obliczeniowa GPU to setki procesorów chciwych na obliczenia, przy tworzeniu programu - jądra (jądra) - ciężar rozłożenia na nich obciążenia spada na barki programisty. Pomyłka może spowodować, że większość z tych cennych zasobów będzie bezczynna bez powodu. Teraz wyjaśnię dlaczego. Musisz zacząć z daleka.

Przypomnę, że osnowa ( osnowa w terminologii NVidii, fala - w terminologii AMD) - zestaw wątków, które jednocześnie wykonują tę samą funkcję jądra na procesorze. Wątki, zjednoczone przez programistę w bloki, są dzielone na wypaczenia przez planistę wątków (oddzielnie dla każdego wieloprocesora) - podczas gdy jedno wypaczenie jest uruchomione, drugie czeka na przetworzenie żądań pamięci itp. Jeśli niektóre wątki osnowy nadal wykonują obliczenia, podczas gdy inne wykonały już wszystko, co w ich mocy, oznacza to nieefektywne wykorzystanie zasobów obliczeniowych – popularnie określane jako bezczynność.

Każdy punkt synchronizacji, każda gałąź logiki może stworzyć taką bezczynną sytuację. Maksymalna rozbieżność (rozgałęzienie logiki wykonania) zależy od wielkości osnowy. W przypadku procesorów graficznych NVidia jest to 32, dla AMD 64.

Aby skrócić przestoje wieloprocesorowe podczas wykonywania wypaczeń:

  • zminimalizować bariery czasu oczekiwania
  • zminimalizować rozbieżność logiki wykonania w funkcji jądra

Aby skutecznie rozwiązać ten problem, warto zrozumieć, w jaki sposób powstają wypaczenia (w przypadku kilku wymiarów). W rzeczywistości kolejność jest prosta - najpierw w X, potem w Y, a na końcu w Z.

rdzeń uruchamiany jest z bloków 64×16, nitki podzielone są na osnowy w kolejności X, Y, Z - tj. pierwsze 64 elementy są podzielone na dwie osnowy, potem drugi i tak dalej.

Jądro zaczyna się od bloków 16x64. Pierwsze i drugie 16 elementów są dodawane do pierwszej osnowy, trzeci i czwarty element są dodawane do drugiej osnowy i tak dalej.

Jak zmniejszyć rozbieżności (pamiętaj - rozgałęzienia nie zawsze są przyczyną krytycznej utraty wydajności)

  • gdy sąsiednie wątki mają różne ścieżki wykonania - wiele warunków i przejść na nich - poszukaj sposobów na restrukturyzację
  • poszukaj niezrównoważonego obciążenia wątków i zdecydowanie go usuń (jest to wtedy, gdy nie tylko mamy warunki, ale z powodu tych warunków pierwszy wątek zawsze coś oblicza, a piąty nie wchodzi w ten stan i jest bezczynny)

Jak najlepiej wykorzystać zasoby GPU

Zasoby GPU niestety również mają swoje ograniczenia. A ściśle mówiąc, przed uruchomieniem funkcji jądra sensowne jest zdefiniowanie limitów i uwzględnienie tych limitów podczas dystrybucji obciążenia. Dlaczego to jest ważne?

Karty graficzne mają ograniczenia dotyczące całkowitej liczby wątków, które może wykonać jeden wieloprocesor, maksymalnej liczby wątków w jednym bloku, maksymalnej liczby wypaczeń na jednym procesorze, ograniczeń dotyczących różnych typów pamięci itp. Wszystkie te informacje można zażądać zarówno programowo, za pośrednictwem odpowiedniego interfejsu API, jak i wcześniej za pomocą narzędzi z zestawu SDK. (moduły deviceQuery dla urządzeń NVidia, moduły CLInfo dla kart graficznych AMD).

Ogólna praktyka:

  • liczba bloków wątków/grup roboczych musi być wielokrotnością liczby procesorów strumieniowych
  • rozmiar bloku/grupy roboczej musi być wielokrotnością rozmiaru wypaczenia

Jednocześnie należy pamiętać, że absolutne minimum - 3-4 warp / wayfronty wirują jednocześnie na każdym procesorze, mądrzy przewodnicy radzą, aby postępować z uwzględnieniem - co najmniej siedem wayfrontów. Jednocześnie - nie zapomnij o ograniczeniach dotyczących żelazka!

Przechowywanie tych wszystkich szczegółów w głowie szybko staje się nudne, dlatego do obliczania zajętości GPU NVidia zaoferowała nieoczekiwane narzędzie - Excel (!) Kalkulator pełen makr. Można tam wpisać informacje o maksymalnej liczbie wątków dla SM, liczbie rejestrów oraz wielkości pamięci współdzielonej (współdzielonej) dostępnej na procesor strumieniowy, oraz parametry użyte do uruchamiania funkcji - a to daje procent efektywności wykorzystania zasobów (a ty wyrywasz sobie włosy z głowy zdając sobie sprawę, że nie masz wystarczającej liczby rejestrów, aby wykorzystać wszystkie rdzenie).

informacje o użytkowaniu:
http://docs.nvidia.com/cuda/cuda-c-best-practices-guide/#calculating-occupancy

Operacje na GPU i pamięci

Karty graficzne są zoptymalizowane do obsługi pamięci 128-bitowej. Tych. w idealnym przypadku każda manipulacja pamięcią, najlepiej, powinna zmienić 4 czterobajtowe wartości na raz. Główną irytacją programisty jest to, że nowoczesne kompilatory dla GPU nie są w stanie zoptymalizować takich rzeczy. Musi to być zrobione bezpośrednio w kodzie funkcji i, średnio, przynosi ułamki procentowego wzrostu wydajności. Częstotliwość żądań pamięci ma znacznie większy wpływ na wydajność.

Problem jest następujący – każde żądanie zwraca w odpowiedzi fragment danych, który jest wielokrotnością 128 bitów. A każdy wątek używa tylko jednej czwartej (w przypadku normalnej czterobajtowej zmiennej). Gdy sąsiednie wątki jednocześnie pracują z danymi umieszczonymi sekwencyjnie w komórkach pamięci, zmniejsza to całkowitą liczbę dostępów do pamięci. Zjawisko to nazywa się połączonymi operacjami odczytu i zapisu ( dostęp koalescencyjny - dobrze! zarówno czytać, jak i pisać) - i z prawidłową organizacją kodu ( szybki dostęp do ciągłego fragmentu pamięci — zły!) może znacznie poprawić wydajność. Organizując jądro - pamiętaj - ciągły dostęp - w obrębie elementów jednego rzędu pamięci, praca z elementami kolumny nie jest już tak wydajna. Chcesz więcej szczegółów? Podobał mi się ten pdf - lub google dla " techniki łączenia pamięci “.

Czołowe miejsce w nominacji „wąskie gardło” zajmuje kolejna operacja pamięciowa - kopiowanie danych z pamięci hosta do GPU . Kopiowanie i tak nie odbywa się, ale z obszaru pamięci specjalnie przydzielonego przez sterownik i system: gdy pojawia się żądanie skopiowania danych, system najpierw kopiuje je tam, a dopiero potem przesyła je do GPU. Szybkość przesyłania danych jest ograniczona przepustowością Magistrala PCI Express xN (gdzie N to liczba linii danych), przez które nowoczesne karty graficzne komunikują się z hostem.

Jednak dodatkowe kopiowanie wolnej pamięci na hoście jest czasami nieuzasadnionym obciążeniem. Wyjściem jest skorzystanie z tzw przypięta pamięć - specjalnie oznaczony obszar pamięci, aby system operacyjny nie był w stanie wykonywać z nim żadnych operacji (na przykład wyładować, aby wymienić / przenieść według własnego uznania itp.). Transfer danych z hosta na kartę graficzną odbywa się bez udziału systemu operacyjnego - asynchronicznie, poprzez DMA (bezpośredni dostęp do pamięci).

I na koniec trochę więcej o pamięci. Pamięć współdzielona na wieloprocesorze jest zwykle zorganizowana w postaci banków pamięci zawierających 32-bitowe słowa - dane. Liczba banków tradycyjnie różni się w zależności od generacji GPU - 16/32 Jeśli każdy wątek żąda danych z oddzielnego banku, wszystko jest w porządku. W przeciwnym razie uzyskuje się kilka żądań odczytu / zapisu do jednego banku i otrzymujemy - konflikt ( konflikt banku pamięci współdzielonej). Takie sprzeczne wywołania są serializowane, a zatem wykonywane sekwencyjnie, a nie równolegle. Jeśli wszystkie wątki uzyskują dostęp do tego samego banku, używana jest odpowiedź „broadcast” ( audycja) i nie ma konfliktu. Sposobów na skuteczne radzenie sobie z konfliktami dostępu jest kilka, spodobało mi się opis głównych technik pozbywania się konfliktów dostępu do banków pamięci – .

Jak sprawić, by operacje matematyczne były jeszcze szybsze? Zapamietaj to:

  • obliczenia podwójnej precyzji są dużym obciążeniem operacyjnym z fp64 >> fp32
  • stałe postaci 3.13 w kodzie domyślnie są interpretowane jako fp64, jeśli nie podasz jawnie 3.14f
  • aby zoptymalizować matematykę, nie będzie zbyteczne konsultowanie się w przewodnikach - i czy są jakieś flagi dla kompilatora
  • dostawcy umieszczają w swoich pakietach SDK funkcje, które wykorzystują funkcje urządzenia w celu osiągnięcia wydajności (często kosztem przenośności)

Programiści CUDA powinni zwracać szczególną uwagę na tę koncepcję potok cuda, pozwalając na uruchamianie kilku podstawowych funkcji jednocześnie na jednym urządzeniu lub łączenie asynchronicznego kopiowania danych z hosta na urządzenie podczas wykonywania funkcji. OpenCL nie zapewnia jeszcze takiej funkcjonalności 🙁

Profilowanie śmieci:

NVifia Visual Profiler to ciekawe narzędzie, które analizuje zarówno jądra CUDA, jak i OpenCL.

PS Jako dłuższy przewodnik po optymalizacji mogę polecić wszelkiego rodzaju google przewodnik po najlepszych praktykach dla OpenCL i CUDA.

  • ,

Mówiąc o obliczeniach równoległych na GPU, musimy pamiętać, w jakich czasach żyjemy, dziś jest czas, kiedy wszystko na świecie przyspiesza tak bardzo, że tracimy poczucie czasu, nie zauważając jak pędzi. Wszystko co robimy wiąże się z dużą dokładnością i szybkością przetwarzania informacji, w takich warunkach z pewnością potrzebujemy narzędzi, aby przetworzyć wszystkie posiadane informacje i przetworzyć je na dane, poza tym mówiąc o takich zadaniach, musimy pamiętać, że te zadania są niezbędne nie tylko dla dużych organizacji czy megakorporacji, ale także zwykli użytkownicy muszą teraz rozwiązywać takie problemy, którzy rozwiązują swoje ważne zadania związane z wysokimi technologiami w domu na komputery osobiste! Pojawienie się NVIDIA CUDA nie było zaskakujące, a raczej uzasadnione, bo od razu konieczne będzie przetwarzanie znacznie bardziej czasochłonnych zadań na PC niż dotychczas. Praca, która wcześniej zajmowała bardzo dużo czasu, teraz zajmie odpowiednio kilka minut, co wpłynie na ogólny obraz całego świata!

Co to jest przetwarzanie GPU

Obliczenia na GPU to wykorzystanie GPU do wykonywania zadań technicznych, naukowych i codziennych. Obliczenia na GPU polegają na wykorzystaniu CPU i GPU z heterogenicznym wyborem pomiędzy nimi, a mianowicie: sekwencyjna część programów jest przejmowana przez CPU, podczas gdy czasochłonne zadania obliczeniowe pozostają przez GPU. Dzięki temu zadania są zrównoleglone, co prowadzi do szybszego przetwarzania informacji i skraca czas wykonania pracy, system staje się bardziej produktywny i może jednocześnie przetwarzać więcej zadań niż dotychczas. Jednak do osiągnięcia takiego sukcesu nie wystarczy sama obsługa sprzętu, w tym przypadku potrzebna jest również obsługa oprogramowania, aby aplikacja mogła przenieść na GPU najbardziej czasochłonne obliczenia.

Co to jest CUDA

CUDA to technologia programowania algorytmów w uproszczonym języku C, która działa na procesorach graficznych ósmej generacji i starszych akceleratorach GeForce, a także na odpowiednich kartach Quadro i Tesla firmy NVIDIA. CUDA pozwala na włączenie specjalnych funkcji do tekstu programu C. Funkcje te są napisane w uproszczonym języku programowania C i działają na GPU. Pierwsza wersja CUDA SDK została wydana 15 lutego 2007 roku. Aby pomyślnie przetłumaczyć kod w tym języku, CUDA SDK zawiera własny kompilator C wiersz poleceń nvcc od NVIDIA. Kompilator nvcc jest oparty na otwarty kompilator Open64 i jest przeznaczony do tłumaczenia kodu hosta (głównego, sterującego) i urządzenia (kodu sprzętowego) (pliki z rozszerzeniem .cu) na pliki obiektowe odpowiednie do zbudowania finalnego programu lub biblioteki w dowolnym środowisku programistycznym, na przykład , w Microsoft Visual Studio.

Możliwości technologiczne

  1. Standardowy język C do równoległego tworzenia aplikacji GPU.
  2. Gotowe biblioteki analiz numerycznych do szybkiej transformacji Fouriera oraz podstawowy pakiet programów do algebry liniowej.
  3. Dedykowany sterownik CUDA do obliczeń z szybkim transferem danych między GPU a CPU.
  4. Możliwość interakcji sterownika CUDA ze sterownikami graficznymi OpenGL i DirectX.
  5. Wsparcie sali operacyjnej Systemy Linux 32/64-bitowy, Windows XP 32/64-bitowy i MacOS.

Korzyści technologiczne

  1. Interfejs programowania aplikacji CUDA (CUDA API) jest oparty na standardowym języku programowania C z pewnymi ograniczeniami. Upraszcza to i wygładza proces uczenia się architektury CUDA.
  2. 16 KB pamięci współdzielonej między wątkami można wykorzystać do zorganizowanej przez użytkownika pamięci podręcznej o większej przepustowości niż podczas pobierania ze zwykłych tekstur.
  3. Wydajniejsze transakcje między pamięcią procesora a pamięcią wideo.
  4. Pełna obsługa sprzętowa operacji na liczbach całkowitych i bitowych.

Przykład zastosowania technologii

cRark

Najtrudniejszą częścią tego programu jest nalewka. Program ma interfejs konsoli, ale dzięki instrukcjom dołączonym do samego programu można go używać. Poniżej znajduje się krótka instrukcja do konfiguracji programu. Przetestujemy program pod kątem wydajności i porównamy go z innym podobnym programem, który nie korzysta z NVIDIA CUDA, w tym przypadku jest to dobrze znany program „Advanced Archive Password Recovery”.

Z pobranego archiwum cRark potrzebujemy tylko trzech plików: crark.exe , crark-hp.exe i password.def . Сrark.exe to łamacz haseł RAR 3.0 bez zaszyfrowanych plików w archiwum (tj. Otwierając archiwum, widzimy nazwy, ale nie możemy rozpakować archiwum bez hasła).

Сrark-hp.exe to narzędzie wiersza polecenia do łamania haseł RAR 3.0, które szyfruje całe archiwum (tj. podczas otwierania archiwum nie widzimy ani nazwy, ani samych archiwów i nie możemy rozpakować archiwum bez hasło).

Password.def to dowolny plik tekstowy o zmienionej nazwie z bardzo małą zawartością (na przykład: 1st line: ## 2nd line: ?* , w którym to przypadku hasło zostanie złamane przy użyciu wszystkich znaków). Password.def jest głową programu cRark. Plik zawiera zasady łamania hasła (lub obszar znaków, których użyje crark.exe w swojej pracy). Więcej szczegółów na temat opcji wyboru tych znaków można znaleźć w pliku tekstowym uzyskanym po otwarciu pobranego ze strony autora programu cRark: russian.def.

Trening

Muszę od razu powiedzieć, że program działa tylko wtedy, gdy twoja karta graficzna jest oparta na GPU z obsługą poziomu akceleracji CUDA 1.1. Tak więc seria kart graficznych opartych na chipie G80, takich jak GeForce 8800 GTX , nie wchodzi w rachubę, ponieważ mają sprzętową obsługę akceleracji CUDA 1.0. Za pomocą CUDA program wybiera tylko hasła do archiwów RAR w wersjach 3.0+. Należy zainstalować całe oprogramowanie związane z CUDA, a mianowicie:

Tworzymy dowolny folder w dowolnym miejscu (na przykład na dysku C:) i nazywamy go dowolną nazwą, na przykład „3.2”. Umieściliśmy tam pliki: crark.exe , crark-hp.exe i password.def oraz chronione hasłem / zaszyfrowane archiwum RAR.

Następnie należy uruchomić konsolę wiersza poleceń systemu Windows i przejść do utworzonego w niej folderu. W Windows Vista i 7 należy wywołać menu „Start” i wpisać „cmd.exe” w polu wyszukiwania; w systemie Windows XP z menu „Start” należy najpierw wywołać okno dialogowe „Uruchom” i wpisać „cmd”. .exe”. Po otwarciu konsoli wpisz polecenie takie jak: cd C:\folder\ , w tym przypadku cd C:\3.2.

Rekrutacja w Edytor tekstu dwie linie (można też zapisać tekst jako plik .bat w folderze z cRark), aby odgadnąć hasło do zabezpieczonego hasłem archiwum RAR z niezaszyfrowanymi plikami:

echo wyłączone;
cmd /K crark (nazwa archiwum).rar

odgadnąć hasło do zabezpieczonego hasłem i zaszyfrowanego archiwum RAR:

echo wyłączone;
cmd /K crark-hp (nazwa archiwum).rar

Skopiuj 2 wiersze plik tekstowy do konsoli i naciśnij Enter (lub uruchom plik .bat).

wyniki

Proces deszyfrowania pokazano na rysunku:

Szybkość selekcji w cRarku za pomocą CUDA wynosiła 1625 haseł na sekundę. W ciągu jednej minuty, trzydziestu sześciu sekund odgadano hasło składające się z 3 znaków: "q)$". Dla porównania: współczynnik brutalnej siły w Advanced Archive Password Recovery na moim dwurdzeniowym procesorze Athlon 3000+ wynosi maksymalnie 50 haseł na sekundę, a brutalna siła zajęłaby 5 godzin. Oznacza to, że wybór archiwum RAR przez bruteforce w cRark przy użyciu karty graficznej GeForce 9800 GTX+ jest 30 razy szybszy niż na procesorze.

Dla tych, którzy mają procesor Intel, dobrą płytę główną z wysoką częstotliwością magistrali systemowej (FSB 1600 MHz), szybkość procesora i brutalna siła będą wyższe. A jeśli masz czterordzeniowy procesor i parę kart graficznych na poziomie GeForce 280 GTX, to wydajność brutalnej siły hasła jest czasami przyspieszona. Podsumowując przykład, trzeba powiedzieć, że problem ten został rozwiązany przy użyciu technologii CUDA w zaledwie 2 minuty zamiast 5 godzin, co wskazuje na duży potencjał tej technologii!

wnioski

Rozważając dzisiaj technologię obliczeń równoległych CUDA, wyraźnie widzieliśmy całą moc i ogromny potencjał rozwoju tej technologii na przykładzie programu do odzyskiwania haseł dla archiwa RAR. Muszę powiedzieć o perspektywach tej technologii, ta technologia z pewnością znajdzie miejsce w życiu każdej osoby, która zdecyduje się z niej skorzystać, czy będą to zadania naukowe, czy zadania związane z obróbką wideo, czy nawet zadania ekonomiczne, które wymagają szybkich dokładnych obliczeń, wszystko to doprowadzi do nieuniknionego wzrostu nakładu pracy wydajność, której nie można ignorować. Do dziś w leksykonie zaczyna już pojawiać się fraza „domowy superkomputer”; jest absolutnie oczywiste, że aby przełożyć taki przedmiot na rzeczywistość, każdy dom ma już narzędzie o nazwie CUDA. Od czasu premiery kart opartych na chipie G80 (2006), duża ilość Akceleratory oparte na technologii NVIDIA obsługujące technologię CUDA, dzięki którym marzenie o superkomputerze w każdym domu może stać się rzeczywistością. Promując technologię CUDA, NVIDIA podnosi swoją wiarygodność w oczach klientów w postaci dostarczania dodatkowych funkcji do swojego sprzętu, który wielu już zakupiło. Pozostaje tylko wierzyć, że już niedługo CUDA bardzo szybko się rozwinie i pozwoli użytkownikom w pełni wykorzystać wszystkie możliwości obliczeń równoległych na GPU.