Spróbuję podsumować moje doświadczenia zdobyte podczas tworzenia ViennaCL, gdzie mamy backendy CUDA i OpenCL, głównie z tłumaczeniami wielu jąder obliczeniowych 1: 1. Z twojego pytania zakładam również, że głównie zajmujemy się tutaj procesorami graficznymi.
Przenośność wydajności.Po pierwsze, nie ma czegoś takiego jak jądra przenośne pod względem wydajności w tym sensie, że pisze się je raz i będzie działać wydajnie na każdym sprzęcie. Nie w OpenCL, gdzie jest to bardziej widoczne ze względu na szerszy zakres obsługiwanego sprzętu, ale także nie w CUDA. W CUDA jest to mniej widoczne ze względu na mniejszy zakres obsługiwanego sprzętu, ale nawet tutaj musimy rozróżnić co najmniej trzy architektury sprzętowe (pre-Fermi, Fermi, Kepler). Te fluktuacje wydajności mogą z łatwością spowodować 20-procentową zmienność wydajności w zależności od sposobu organizacji wątków i wybranych grup roboczych, nawet jeśli jądro jest tak proste jak kopia buforowa. Prawdopodobnie warto również wspomnieć, że na procesorach graficznych wcześniejszych niż Fermi i Fermi można było pisać szybkie jądra mnożenia macierzy i macierzy bezpośrednio w CUDA, podczas gdy w przypadku najnowszych układów GPU Keplera wydaje się, że należy przejść do języka pseudo-montażu PTX, aby zbliżyć się do wydajności CUBLAS. Tak więc wydaje się, że nawet język kontrolowany przez dostawcę, taki jak CUDA, ma problemy z nadążeniem za rozwojem sprzętu. Co więcej, cały kod CUDA jest kompilowany statycznie po uruchomieniu nvcc, co nieco wymaga równoważenia poprzez flagę -arch, podczas gdy jądra OpenCL są kompilowane w czasie wykonywania z kompilatora just-in-time, więc możesz w zasadzie dostosować jądra aż do specyfiki konkretnego urządzenia obliczeniowego. Ta ostatnia jest jednak dość zaangażowana i zwykle staje się bardzo atrakcyjną opcją w miarę dojrzewania kodu i gromadzenia doświadczeń. Cena do zapłaty to czas O (1) wymagany do kompilacji dokładnie na czas, co może być problemem w niektórych sytuacjach. OpenCL 2.
Debugowanie i profilowanie. Narzędzia do debugowania i profilowania CUDA są najlepsze dostępne dla GPGPU. Narzędzia AMD też nie są złe, ale nie zawierają klejnotów takich jak cuda-gdb czy cuda-memcheck. Ponadto, do dziś NVIDIA zapewnia najbardziej niezawodne sterowniki i zestawy SDK dla GPGPU, zawieszanie się systemu z powodu błędnych jąder jest naprawdę wyjątkiem, a nie regułą, zarówno w OpenCL, jak i CUDA. Z powodów, których prawdopodobnie nie muszę tutaj wyjaśniać, NVIDIA nie oferuje już debugowania i profilowania dla OpenCL z CUDA 5.0 i nowszymi.
Dostępność i wygoda. O wiele łatwiej jest uruchomić pierwsze kody CUDA, zwłaszcza że kod CUDA dość dobrze integruje się z kodem hosta. (Cena omówię później). W Internecie jest wiele samouczków, a także przewodniki po optymalizacji i niektóre biblioteki. Z OpenCL musisz przejść sporo kodu inicjującego i napisać jądra w ciągach, więc błędy kompilacji znajdziesz tylko podczas wykonywania, gdy podajesz źródła do kompilatora jit. Zatem przejście przez jeden cykl kodu / kompilacji / debugowania w OpenCL zajmuje więcej czasu, więc produktywność jest zwykle niższa na tym początkowym etapie programowania.
Aspekty biblioteki oprogramowania. Podczas gdy poprzednie elementy były na korzyść CUDA, integracja z innym oprogramowaniem jest dużym plusem dla OpenCL. Możesz użyć OpenCL po prostu łącząc się ze wspólną biblioteką OpenCL i to wszystko, podczas gdy w CUDA musisz mieć cały łańcuch narzędzi CUDA dostępny. Co gorsza, aby nvcc działało, musisz użyć poprawnych kompilatorów hosta. Jeśli kiedykolwiek próbowałeś użyć np. CUDA 4.2 z GCC 4.6 lub nowszą wersją, ciężko Ci będzie pracować. Zasadniczo, jeśli zdarzy się, że używasz kompilatora nowszego niż CUDA SDK, mogą wystąpić problemy. Integracja z systemami kompilacji, takimi jak CMake, jest kolejnym źródłem bólu głowy (można również znaleźć wiele dowodów na np. PETSclisty mailingowe). To może nie być problem na twoim komputerze, na którym masz pełną kontrolę, ale jak tylko rozpowszechnisz swój kod, napotkasz sytuacje, w których użytkownicy są nieco ograniczeni w stosie oprogramowania. Innymi słowy, dzięki CUDA nie możesz już wybierać swojego ulubionego kompilatora hosta, ale NVIDIA decyduje, które kompilatory możesz używać.
Inne aspekty. CUDA jest trochę bliżej sprzętu (np. Wypaczenia), ale moje doświadczenie z algebrą liniową jest takie, że rzadko uzyskuje się z tego znaczącą korzyść. Istnieje jeszcze kilka bibliotek oprogramowania dla CUDA, ale coraz więcej bibliotek korzysta z wielu backendów obliczeniowych. Tymczasem ViennaCL , VexCL lub Paralution obsługują backendy OpenCL i CUDA, podobny trend można zaobserwować w przypadku bibliotek w innych obszarach.
GPGPU nie jest srebrną kulą. Wykazano, że GPGPU zapewnia dobrą wydajność dla operacji ustrukturyzowanych i zadań o ograniczonej mocy obliczeniowej. Jednak w przypadku algorytmów z niemałym udziałem przetwarzania sekwencyjnego GPGPU nie może magicznie pokonać Prawa Amdahla . W takich sytuacjach lepiej jest zastosować dobrą implementację procesora najlepszego dostępnego algorytmu niż próbować rzucić równoległy, ale mniej odpowiedni algorytm na swój problem. Ponadto PCI-Express jest poważnym wąskim gardłem, dlatego należy wcześniej sprawdzić, czy oszczędności wynikające z GPU mogą zrekompensować obciążenie związane z przenoszeniem danych tam i z powrotem.
Moja rekomendacja. Proszę wziąć pod uwagę CUDA i OpenCL zamiast CUDA lubOpenCL. Nie trzeba niepotrzebnie ograniczać się do jednej platformy, ale zamiast tego wyciągnąć to, co najlepsze z obu światów. Dla mnie najlepsze jest skonfigurowanie początkowej implementacji w CUDA, debugowanie, profilowanie, a następnie przeniesienie jej do OpenCL za pomocą prostych podstawień łańcuchów. (Możesz nawet sparametryzować swoje procedury generowania łańcucha jądra OpenCL, aby mieć pewną elastyczność w dostosowywaniu do docelowego sprzętu.) Ten wysiłek związany z przenoszeniem zwykle zajmuje mniej niż 10 procent twojego czasu, ale daje ci również możliwość działania na innym sprzęcie. Możesz być zaskoczony, jak dobrze sprzęt inny niż NVIDIA może działać w określonych sytuacjach. Przede wszystkim rozważ ponowne wykorzystanie funkcjonalności w bibliotekach w możliwie największym stopniu. Podczas gdy szybki i brudna ponowna implementacja niektórych funkcji często działa akceptowalnie w przypadku wykonywania jednowątkowego na procesorze, często daje słabą wydajność na bardzo równoległym sprzęcie. Idealnie możesz nawet przenieść wszystko do bibliotek i nigdy nie musisz się martwić, czy używają CUDA, OpenCL, czy obu z nich wewnętrznie. Osobiście nigdy nie odważyłbym się napisać kodu zablokowanego przez dostawcę dla czegoś, na czym chcę polegać za kilka lat, ale ten aspekt ideologiczny powinien zostać omówiony osobno.