• Nie Znaleziono Wyników

Przyspieszeniem nazywany jest stosunek czasu wykonania algorytmu sekwencyj- sekwencyj-nego oraz czasu wykonania algorytmu równoległego

1.2. Model obliczeniowy

Istnieje wiele modeli obliczeniowych różniących się pomiędzy sobą m.in. zbiorem operacji podstawowych i założeniami dotyczącymi organizacji pamięci. W niniejszej pracy rozważania prowadzone będą przy założeniu modelu Word RAM, który stosunkowo dobrze oddaje cechy współczesnych komputerów. Model ten jest rozwinięciem modelu RAM (Random Access Ma-chine) zaproponowanego w 1973 przez Cooka i Reckhowa [47]. W modelu Word RAM zwykle zakłada się, że pamięć operacyjna o dostępie swobodnym składa się z w-bitowych słów, gdzie w≥ logn, a n jest rozmiarem danych1. Założenie to powoduje, że każdy wskaźnik lub indeks do danych można zapisać na pojedynczym słowie komputerowym. Każda z operacji podsta-wowych, do których należą: dodawanie, odejmowanie, mnożenie2, operacje bitowe (| – suma,

& – iloczyn, ⊕ – różnica symetryczna,  ,  – przesunięcia, ∼ – negacja wartości wszystkich bitów), odczyt i zapis z/do pamięci wykonuje się w czasie O(1) i działa na O(1) komórkach pamięci [177].

Czasami stosowany jest także nieco bardziej rygorystyczny model AC0 RAM, w którym można wykonywać tylko takie operacje podstawowe, które można fizycznie zrealizować za po-mocą układu bramek logicznych o nieograniczonej liczbie wejść z zachowaniem stałej głęboko-ści układu. W praktyce powoduje to, że dopuszczalny jest zbiór operacji z modelu Word RAM z wyjątkiem mnożenia. Przyjęte założenie dotyczące modelu ma oczywiście konsekwencje, je-śli chodzi o złożoności czasowe algorytmów. Jednym z przykładów może być struktura danych

1W tekście pracy przyjęto, że log oznacza log2.

2Niektórzy autorzy, np. Papadimitriou [169], nie zaliczają mnożenia do operacji podstawowych w modelu Word RAM. Inni autorzy, np. Cormen i in. [48], do zbioru operacji podstawowych modelu Word RAM zaliczają też dzielenie.

1.3. Obliczenia ogólnego przeznaczenia na procesorach graficznych 29 van Emde Boasa [209, 210] stosowana często w algorytmach proponowanych w niniejszej pra-cy. Otwartym problemem pozostaje, czy możliwa jest jej implementacja w modelu AC0 RAM przy takich samych złożonościach czasowych jak w modelu Word RAM. Dobry przegląd, także w ujęciu historycznym, modeli obliczeniowych można znaleźć w pracy van Emde Boasa [208]

oraz w artykule Allendera [7]. Ciekawą dyskusję, dotyczącą wpływu wyboru modelu oblicze-niowego na złożoność czasową operacji wyznaczania poprzednika elementu w uporządkowa-nym zbiorze, która to operacja (właściwie operacja do niej symetryczna: wyznaczanie następ-nika) będzie często wykorzystywana w algorytmach niniejszej pracy, można znaleźć w [177].

W pracy będą także rozważane algorytmy równoległe, dla których istnieje kilka modeli obliczeniowych i trudno wskazać jeden dominujący model. Wynika to z faktu, że w prakty-ce korzysta się z zasadniczo różnych architektur równoległych, które modelowane są w różny sposób. Jednym z popularniejszych modeli jest model PRAM (Parallel Random Access Machi-ne) [187], w którym zakłada się, że wiele procesorów współpracuje ze sobą za pomocą wspólnej pamięci. W jego ramach wyróżnia się kilka klas w zależności od sposobu dostępu do pamięci.

Model ten jest stosunkowo dobrym przybliżeniem współczesnych systemów komputerowych, w których procesor centralny zawiera wiele rdzeni pracujących ze wspólną pamięcią operacyj-ną. Innym interesującym modelem jest model BSP (Bulk-Synchronous Parallelism), w którym pidentycznych par procesor-pamięć komunikuje się za pomocą sieci komputerowej [207]. Mo-del ten stosunkowo dobrze opisuje sytuację, w której komputery tworzące klaster obliczeniowy łączone są w sieć. Również architekturę wielordzeniową można opisywać za pomocą modelu BSP. W tej sytuacji wspólna pamięć operacyjna pełni rolę sieci łączącej poszczególne rdzenie.

Głównym tematem niniejszej pracy są algorytmy sekwencyjne oraz algorytmy oparte na równoległości bitowej. Rozważane są także ich wersje równoległe dla specyficznej architektu-ry procesorów graficznych. Tym samym, podczas analizy algoarchitektu-rytmów nie będą wykorzystane wymienione wcześniej modele obliczeń równoległych. O modelach tych wspomniano dlate-go, że część istniejących algorytmów równoległych służących do rozwiązywania problemów omawianych w niniejszej pracy zostało zaproponowanych dla takich właśnie modeli, a niektóre z nich były badane teoretycznie przy założeniu pewnych parametrów modeli. Algorytmy te sta-nowiły inspirację dla tworzenia algorytmów przeznaczonych do wykonywania z zastosowaniem procesorów graficznych.

1.3. Obliczenia ogólnego przeznaczenia na procesorach graficznych

Architektura współczesnych procesorów centralnych może być scharakteryzowana w nastę-pujący sposób. Procesor składa się z kilku niezależnych rdzeni (najczęściej od 2 do 6). Każdy z rdzeni jest niezależną jednostką obliczeniową wyposażoną w pamięć podręczną (ang. cache

memory) zorganizowaną w kilka poziomów (na niektórych poziomach pamięć ta może być wspólna dla rdzeni). Rozmiar tej szybkiej pamięci podręcznej to aktualnie kilka megabajtów.

Dostępna jest także pamięć RAM (ang. Random Access Memory) wspólna dla wszystkich rdze-ni. Jej rozmiar jest rzędu pojedynczych gigabajtów.

Architektura procesorów graficznych jest zupełnie inna. Opis architektury procesorów GPU zostanie przedstawiony na przykładzie procesorów firmy NVidia, z uwagi na to, że dla tych pro-cesorów wykonano implementacje algorytmów, jednak architektura propro-cesorów graficznych, drugiej wiodącej na tym rynku, firmy AMD/ATI jest podobna. Pojedynczy procesor GPU skła-da się z dziesiątek multiprocesorów (30 w modelu GTX 280, 15 w modelu GTX 480). Każdy z multiprocesorów zawiera od kilku do kilkudziesięciu rdzeni (8 w modelu GTX 280, 32 w mo-delu GTX 480), które realizują obliczenia zgodnie z modelem SIMT (ang. single instruction multiple thread), podobnym do modelu SIMD (ang. single instruction multiple data) w tak-sonomii Flynna [90]. Każdy multiprocesor wyposażony jest w niewielkich rozmiarów pamięć wspólną (ang. shared memory) (16 KB w GTX 280), z którą jest związana pamięć podręczna.

Pamięć globalna (ang. global memory), wspólna dla wszystkich multiprocesorów jest rozmiaru pojedynczych gigabajtów i nie jest wyposażona w pamięć podręczną (w starszych modelach, do GTX 285 włącznie), przez co dostęp do niej jest stosunkowo wolny. W najnowszych mode-lach procesorów firmy NVidia, z rodziny Fermi (np. GTX 480) pamięć ta ma pamięć podręczną, której rozmiar wynosi 768 KB. Inne rodzaje pamięci dostępne w procesorach GPU, a niewystę-pujące w procesorach CPU to:

• pamięć lokalna (ang. local memory) – niezależna dla każdego multiprocesora, bez pamięci podręcznej, rozmiar dziesiątek kilobajtów,

• pamięć stała (ang. constant memory) – podobna do pamięci lokalnej, ale o innej charakte-rystyce dostępu, z pamięcią podręczną,

• pamięć tekstur (ang. texture memory) – nie jest to osobny obszar pamięci, a raczej inny sposób dostępu do pamięci globalnej, w którym dzięki wykorzystaniu pamięci podręcznej tekstur możliwy jest nieco szybszy dostęp do pamięci globalnej, przy założeniu że żądane dane w pamięci globalnej ułożone są w specyficzny sposób.

Liczba dostępnych rejestrów w pojedynczym multiprocesorze (16 384 w GTX 280, 32 768 w GTX 480) jest znacznie większa niż w procesorach CPU. Dla efektywnego wykorzystania procesora GPU kluczowy jest właściwy dobór rodzaju pamięci dla każdej struktury danych, jak również sposobu dostępu do pamięci przez równolegle pracujące wątki. Niezadbanie o to może spowodować serializację wątków bądź wykorzystanie tylko niewielkiego ułamka maksymalnej przepustowości pamięci globalnej. Generalnie, pamięć globalna powinna być używana najrza-dziej jak to możliwe, gdyż pojedyncze odwołanie do niej zajmuje 400–800 cykli procesora. Co więcej, kolejne wątki powinny odwoływać się do kolejnych słów pamięci globalnej, co daje

lep-1.3. Obliczenia ogólnego przeznaczenia na procesorach graficznych 31 Tabela 1.1 Charakterystyka procesorów CPU i GPU

Zalety Wady

• łatwa komunikacja pomiędzy wątkami • niewielka liczba rdzeni (do 6)

• duża, wielopoziomowa pamięć podręczna • umiarkowana przepustowość pamięci CPU • niezależne rdzenie (architektura MIMD) • niewielka liczba rejestrów

• wiele języków programowania

• duże doświadczenie w programowaniu CPU

• duża liczba rdzeni • rdzenie zależne (architektura SIMT)

• duża przepustowość pamięci • pamięć globalna bez pamięci podręcznej

• duża liczba rejestrów • mała pamięć wspólna

GPU • ograniczona komunikacja pomiędzy wątkami

• konieczność dbania o efektywność użycia pamięci

• specjalizowane języki programowania

• niewielkie doświadczenie w programowaniu GPU

sze wykorzystanie przepustowości pamięci, dzięki tzw. dostępowi połączonemu (ang. coalesced access). Odwołania do pamięci wspólnej w ramach multiprocesora powinny mieć miejsce bez powodowania konfliktów dostępu do banków pamięci podręcznej. Szczegóły dotyczące efek-tywnego dostępu do pamięci w procesorach GPU można znaleźć w [167].

W procesorach firmy NVidia wątki grupowane są na kilku poziomach. Na poziomie naj-niższym, 32 wątki grupowane są w wiązkę (ang. warp). Kilka wiązek tworzy blok (ang. block).

Każdy blok wątków wykonywany jest w pojedynczym multiprocesorze, a wątki należące do jed-nego bloku mogą być synchronizowane, dzięki czemu mogą one łatwo wymieniać się danymi.

Na najwyższym poziomie, wiele bloków zgrupowanych jest w pojedyncze wywołanie jądra (ang. kernel execution). Liczba bloków powinna być co najmniej równa liczbie multiproce-sorów, gdyż w przeciwnym razie niektóre multiprocesory będą niewykorzystane. Aby zredu-kować opóźnienia w dostępie do pamięci globalnej, rejestrów oraz pamięci wspólnej, zalecane jest, by liczba bloków przekraczała kilkakrotnie liczbę multiprocesorów. Podsumowując, liczba wątków w pojedynczym wywołaniu jądra jest zwykle rzędu dziesiątek tysięcy. Jak widać z po-wyższej charakterystyki, programowanie dla procesorów CPU oraz dla procesorów GPU różni się w sposób zasadniczy. Dobre wprowadzenie do programowania dla procesorów GPU można znaleźć w serii artykułów [85] oraz w książce [127]. Krótkie podsumowanie cech procesorów CPU i GPU znajduje się w tabeli 1.1.

Nie każdy algorytm sekwencyjny nadaje się do zrównoleglania przy użyciu procesorów CPU, w których liczba rdzeni jest niewielka. Znacznie trudniejsza sytuacja ma miejsce, w przy-padku zrównoleglania z zastosowaniem procesorów GPU, ponieważ liczba współpracujących ze sobą wątków powinna być rzędu tysięcy lub dziesiątek tysięcy. W ogólności, najlepszymi kandydatami do równoległego wykonywania w procesorach GPU są algorytmy, które:

• mogą być łatwo zdekomponowane na wiele niezależnych zadań – wątki należące do różnych bloków nie mogą ze sobą współpracować, wobec czego jeśli synchronizacja pomiędzy nimi

jest konieczna, to musi być zrealizowana przez kolejne wywołania jądra, co jest czasochłon-ne,

• wykonują znacznie więcej obliczeń niż odwołań do pamięci – dostępy do pamięci globalnej są kosztowne i ich liczba powinna być redukowana do niezbędnego minimum,

• używają niewielkiej przestrzeni pamięci dla każdego wątku – najlepiej jeśli algorytm naj-pierw kopiuje dane z pamięci globalnej do pamięci wspólnej lub rejestrów, następnie wyko-nuje na tych danych niezbędne obliczenia i wreszcie zapisuje wyniki do pamięci globalnej.

Współcześnie, jedną z głównych wad procesorów GPU jest niewielka ilość szybkiej pa-mięci wspólnej, w związku z tym, mimo iż np. procesor GTX 280 ma 240 rdzeni, to nie nale-ży oczekiwać 240-krotnego przyspieszenia w stosunku do wersji sekwencyjnej wykonywanej w procesorze CPU. Innymi powodami, dla których takie przyspieszenie jest nieosiągalne, są:

około dwukrotnie niższa częstotliwość taktowania zegara procesora, a także krótsze rejestry (32-bitowe), co w niektórych zastosowaniach ma znaczenie.

Wykorzystanie procesorów graficznych do obliczeń ogólnego przeznaczenia (ang. General-Purpose computation on Graphics Processing Units, GPGPU) staje się coraz częstsze, głównie dzięki aktywnej promocji architektury CUDA firmy NVidia [167, 166] oraz powstaniu języka OpenCL [126, 202], który został stworzony z myślą o obliczeniach w środowiskach heteroge-nicznych, m.in. z wykorzystaniem procesorów graficznych. O rosnącym znaczeniu tego kierun-ku rozwoju świadczyć może także fakt, że na temat architektury CUDA prowadzone są wykłady w wielu uniwersytetach na całym świecie [166]. Znamienne jest też to, że w zestawieniu naj-szybszych superkomputerów Top500.Org [201] z listopada 2010 r. na pierwszym, trzecim oraz czwartym miejscu pod względem mocy obliczeniowej zmierzonej testem LINPACK znajdują się komputery, w których większość mocy obliczeniowej pochodzi z zastosowanych procesorów graficznych firmy NVidia.

Na podstawie publikacji dostępnych w literaturze można zauważyć, że osiągane przyspie-szenia dzięki zastosowaniu procesorów GPU zależą silnie od badanego problemu. Do naj-bardziej udanych przykładów zastosowania procesorów graficznych do obliczeń o charakterze ogólnym można zaliczyć, m.in. rozwiązywanie problemu N ciał w astrofizyce [189], rozwiązy-wanie równań dynamicznych [93], symulacje Monte Carlo migracji fotonów [6], komputero-we generowanie hologramów [191]. Bogaty katalog rozwiązań wykorzystujących architekturę CUDA można znaleźć w [166].

Część I