Programowanie równoległe Optymalizacja dostępu do pamięci GPU Elementarne algortymy równoległe. Rafał Skinderowicz



Podobne dokumenty
Programowanie procesorów graficznych GPGPU. Krzysztof Banaś Obliczenia równoległe 1

Programowanie procesorów graficznych GPGPU. Krzysztof Banaś Obliczenia równoległe 1

Programowanie procesorów graficznych GPGPU

Porównanie wydajności CUDA i OpenCL na przykładzie równoległego algorytmu wyznaczania wartości funkcji celu dla problemu gniazdowego

Analiza algorytmów zadania podstawowe

Podstawowe algorytmy i ich implementacje w C. Wykład 9

Moc płynąca z kart graficznych

Przygotowanie kilku wersji kodu zgodnie z wymogami wersji zadania,

Organizacja pamięci w procesorach graficznych

Równoległy algorytm wyznaczania bloków dla cyklicznego problemu przepływowego z przezbrojeniami

Algorytmy Równoległe i Rozproszone Część V - Model PRAM II

Programowanie równoległe

Algorytmy równoległe. Rafał Walkowiak Politechnika Poznańska Studia inżynierskie Informatyka 2010

Architektura komputerów

Programowanie aplikacji równoległych i rozproszonych

Algorytmy równoległe: ocena efektywności prostych algorytmów dla systemów wielokomputerowych

Algorytmy sortujące 1

Podstawy OpenCL część 2

Zadanie 1 Przygotuj algorytm programu - sortowanie przez wstawianie.

Programowanie procesorów graficznych GPGPU

Przetwarzanie Równoległe i Rozproszone

ANALIZA EFEKTYWNOŚCI MNOŻENIA MACIERZY W SYSTEMACH Z PAMIĘCIĄ WSPÓŁDZIELONĄ

Programowanie procesorów graficznych NVIDIA (rdzenie CUDA) Wykład nr 1

Obliczenia równoległe

Programowanie w VB Proste algorytmy sortowania

Tesla. Architektura Fermi

EFEKTYWNOŚĆ MNOŻENIA MACIERZY W SYSTEMACH Z PAMIĘCIĄ WSPÓŁDZIELONĄ

Algorytmy równoległe: ocena efektywności prostych algorytmów dla systemów wielokomputerowych

Algorytmy i Struktury Danych

Programowanie kart graficznych

Strategia "dziel i zwyciężaj"

Wstęp do programowania

Procesory wielordzeniowe (multiprocessor on a chip) Krzysztof Banaś, Obliczenia wysokiej wydajności.

Wykład 1_2 Algorytmy sortowania tablic Sortowanie bąbelkowe

Wysokowydajna implementacja kodów nadmiarowych typu "erasure codes" z wykorzystaniem architektur wielordzeniowych

Struktury danych i złozoność obliczeniowa. Prof. dr hab. inż. Jan Magott

Programowanie Proceduralne

Materiały pomocnicze do laboratorium. 1. Miary oceny efektywności 2. Mnożenie macierzy 3. Znajdowanie liczb pierwszych

Wykład 8. Pamięć wirtualna. Wojciech Kwedlo, Wykład z Systemów Operacyjnych -1- Wydział Informatyki PB

Wstęp do obliczeń równoległych na GPU

Algorytmy dla maszyny PRAM

Algorytmy i struktury danych. Co dziś? Tytułem przypomnienia metoda dziel i zwyciężaj. Wykład VIII Elementarne techniki algorytmiczne

Rekurencja. Dla rozwiązania danego problemu, algorytm wywołuje sam siebie przy rozwiązywaniu podobnych podproblemów. Przykład: silnia: n! = n(n-1)!

Złożoność obliczeniowa algorytmu ilość zasobów komputera jakiej potrzebuje dany algorytm. Pojęcie to

CUDA. cudniejsze przyk ady

CUDA Median Filter filtr medianowy wykorzystujący bibliotekę CUDA sprawozdanie z projektu

Macierzowe algorytmy równoległe

Algorytmy równoległe: prezentacja i ocena efektywności prostych algorytmów dla systemów równoległych

LABORATORIUM 3 ALGORYTMY OBLICZENIOWE W ELEKTRONICE I TELEKOMUNIKACJI. Wprowadzenie do środowiska Matlab

Dynamiczny przydział pamięci w języku C. Dynamiczne struktury danych. dr inż. Jarosław Forenc. Metoda 1 (wektor N M-elementowy)

Projektowanie algorytmów równoległych. Zbigniew Koza Wrocław 2012

Optymalizacja poleceń SQL Metody dostępu do danych

Programowanie równoległe Wprowadzenie do OpenCL. Rafał Skinderowicz

Algorytmy i Struktury Danych.

DYNAMICZNE PRZYDZIELANIE PAMIECI

SYSTEMY OPERACYJNE WYKLAD 4 - zarządzanie pamięcią

Algorytmy i. Wykład 5: Drzewa. Dr inż. Paweł Kasprowski

prowadzący dr ADRIAN HORZYK /~horzyk tel.: Konsultacje paw. D-13/325

Programowanie i struktury danych

Algorytmy i Struktury Danych.

Wstęp do programowania

Programowanie równoległe

tablica: dane_liczbowe

ALGORYTMY I STRUKTURY DANYCH

Wstęp do programowania

Ćwiczenie 1. Wprowadzenie do programu Octave

Obliczenia na stosie. Wykład 9. Obliczenia na stosie. J. Cichoń, P. Kobylański Wstęp do Informatyki i Programowania 266 / 303

Ćwiczenie 1. Wprowadzenie do programu Octave

Wydajność systemów a organizacja pamięci. Krzysztof Banaś, Obliczenia wysokiej wydajności. 1

Sortowanie - wybrane algorytmy

Sortowanie. Bartman Jacek Algorytmy i struktury

/* dołączenie pliku nagłówkowego zawierającego deklaracje symboli dla wykorzystywanego mikrokontrolera */ #include <aduc834.h>

Algorytm. a programowanie -

Definicja. Ciąg wejściowy: Funkcja uporządkowująca: Sortowanie polega na: a 1, a 2,, a n-1, a n. f(a 1 ) f(a 2 ) f(a n )

Algorytm selekcji Hoare a. Łukasz Miemus

Programowanie współbieżne Wykład 2. Iwona Kochańska

Wskaźniki i dynamiczna alokacja pamięci. Spotkanie 4. Wskaźniki. Dynamiczna alokacja pamięci. Przykłady

Analiza algorytmów zadania podstawowe

Języki programowania. Przetwarzanie plików amorficznych Konwencja języka C. Część siódma. Autorzy Tomasz Xięski Roman Simiński

Tablice i struktury. czyli złożone typy danych. Programowanie Proceduralne 1

Programowanie w C++ Wykład 6. Katarzyna Grzelak. 1 kwietnia K.Grzelak (Wykład 6) Programowanie w C++ 1 / 43

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 5 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 41

Teoretyczne podstawy informatyki

Wykorzystanie architektury Intel MIC w obliczeniach typu stencil

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 4 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 44

Algorytmy i złożoności. Wykład 3. Listy jednokierunkowe

Wstęp do programowania

Język ludzki kod maszynowy

Procesy i wątki. Krzysztof Banaś Obliczenia równoległe 1

Programowanie Współbieżne. Algorytmy

Algorytmy i struktury danych. Drzewa: BST, kopce. Letnie Warsztaty Matematyczno-Informatyczne

Tworzenie programów równoległych cd. Krzysztof Banaś Obliczenia równoległe 1

Operacje logiczne i struktury sterujące.

Przetwarzanie sygnałów

Modelowanie procesów współbieżnych

Opis: Instrukcja warunkowa Składnia: IF [NOT] warunek [AND [NOT] warunek] [OR [NOT] warunek].

Zarządzanie pamięcią operacyjną

Architektura potokowa RISC

// Liczy srednie w wierszach i kolumnach tablicy "dwuwymiarowej" // Elementy tablicy są generowane losowo #include <stdio.h> #include <stdlib.

Transkrypt:

Programowanie równoległe Optymalizacja dostępu do pamięci GPU Elementarne algortymy równoległe Rafał Skinderowicz

Optymalizacja dostępu do pamięci Wątki wewnątrz osnów (ang. warps) wykonują jednocześnie odczyt / zapis do pamięci (SIMD) 32 wątki 32 adresy Jeżeli jeden czeka, to wszystkie czekają W zależności od adresów odczyt może być wykonany w jednej transakcji na pamięci albo wielu

Wzorce dostępu do pamięci globalnej Pamięć globalna wątki Łączony (ang. coalesced) dostęp do pamięci Dane odczytywane są segmentami (ang. chunk) nawet jeżeli potrzebujemy tylko jeden element (słowo)

Wzorce dostępu do pamięci globalnej Pamięć globalna wątki Łączony (ang. coalesced) dostęp do pamięci Rozłączny dostęp do pamięci

Łączony dostęp do pamięci 1 // Przykłady łączonego dostępu do pamięci 2 sum += data[ get_global_id(0) ]; 1 // Przykład niełączonego dostępu 2 sum += data[ get_global_id(0) * 4 ];

Wzorce dostępu do pamięci globalnej W architekturze Nvidia Fermi dostępne są dwa rodzaje odczytów: Buforowany (ang. cached) tryb domyślny: próba odczytu z L1, następnie L2, następnie pam. globalnej dane odczytywane są w 128 bajtowych porcjach (32 x 4B) wiersz pam. podręcznej Nie buforowane: próba odczytu z L2, następnie z pam. globalnej dane odczytywane są w porcjach po 32 bajty Jeden rodzaj zapisu do pam. globalnej unieważnienie L1 zapis do L2

Przykładowe wzorce odczytu 32 wątki odczytują kolejno 4-bajtowe słowa Adresy mieszczą się w 1 wierszu pam. podręcznej 128 bajtów przesyłanych magistralą Addresses from a warp... 0 32 64 96 128 160 192 224 256 288 320 352 384 416 448 Memory addresses

Przykładowe wzorce odczytu 32 wątki odczytują 4-bajtowe słowa, adresy wymieszane Adresy mieszczą się w 1 wierszu pam. podręcznej 128 bajtów przesyłanych magistralą addresses from a warp... 0 32 64 96 128 160 192 224 256 288 320 352 384 416 448 Memory addresses

Przykładowe wzorce odczytu 32 wątki odczytują 4-bajtowe słowa, kolejne adresy Adresy mieszczą się w 2 wierszach pam. podręcznej 256 bajtów przesyłanych magistralą (50% transferu użyteczne) addresses from a warp... 0 32 64 96 128 160 192 224 256 288 320 352 384 416 448 Memory addresses

Przykładowe wzorce odczytu 32 wątki odczytują to samo 4-bajtowe słowo Adres mieści się w 1 wierszu pam. podręcznej 128 bajtów przesyłanych magistralą (4/128 = 3.125% transferu użyteczne) addresses from a warp... 0 32 64 96 128 160 192 224 256 288 320 352 384 416 448 Memory addresses

Przykładowe wzorce odczytu 32 wątki odczytują 4 bajtowe słowa pod różnymi, rozproszonymi adresami Adresy mieszczą się w N wierszach pam. podręcznej N 128 bajtów przesyłanych magistralą z czego 128/(N 128) użyteczne addresses from a warp... 0 32 64 96 128 160 192 224 256 288 320 352 384 416 448 Memory addresses

Pamięć współdzielona konflikty Pamięć współdzielona podzielona jest na banki W pojedynczym cyklu można odczytać słowo z każdego banku W przypadku gdy kilka wątków próbuje odczytać dane z tego samego banku następuje konflikt żądania odczytu są szeregowane Bank 0 Bank 1 Bank 2 Bank 3 Bank 4 Bank 5 Bank 6 Bank 7 Bank 15

Pamięć współdzielona konflikty Thread 0 Thread 1 Thread 2 Thread 3 Thread 4 Thread 5 Thread 6 Thread 7 Bank 0 Bank 1 Bank 2 Bank 3 Bank 4 Bank 5 Bank 6 Bank 7 Thread 0 Thread 1 Thread 2 Thread 3 Thread 4 Thread 5 Thread 6 Thread 7 Bank 0 Bank 1 Bank 2 Bank 3 Bank 4 Bank 5 Bank 6 Bank 7 Thread 15 Bank 15 Thread 15 Bank 15 Jeżeli każdy wątek próbuje odczytać dane z innego banku nie ma konfliktu

Pamięć współdzielona konflikty Thread 0 Thread 1 Thread 2 Thread 3 Thread 4 Thread 8 Thread 9 Thread 10 Thread 11 Bank 0 Bank 1 Bank 2 Bank 3 Bank 4 Bank 5 Bank 6 Bank 7 Bank 15 Thread 0 Thread 1 Thread 2 Thread 3 Thread 4 Thread 5 Thread 6 Thread 7 Thread 15 x8 x8 Bank 0 Bank 1 Bank 2 Bank 7 Bank 8 Bank 9 Bank 15 Przykłady konfliktów 2-drożnego oraz 8-drożnego

Konflikty przykład Załóżmy, że pamięć podzielona jest na 4 banki Dana jest macierz int t[4][4] 1 // Komórki macierzy 2 t[0][0] t[0][1] t[0][2] t[0][3] 3 t[1][0] t[1][1] t[1][2] t[1][3] 4 t[2][0] t[2][1] t[2][2] t[2][3] 5 t[3][0] t[3][1] t[3][2] t[3][3] 1 // Numery banków komórek 2 0 1 2 3 3 0 1 2 3 4 0 1 2 3 5 0 1 2 3

Konflikty przykład Załóżmy, że pamięć podzielona jest na 4 banki Dana jest macierz int t[4][4] 1 // Komórki macierzy 2 t[0][0] t[0][1] t[0][2] t[0][3] 3 t[1][0] t[1][1] t[1][2] t[1][3] 4 t[2][0] t[2][1] t[2][2] t[2][3] 5 t[3][0] t[3][1] t[3][2] t[3][3] 1 // Numery banków komórek 2 0 1 2 3 3 0 1 2 3 4 0 1 2 3 5 0 1 2 3 Przy próbie zapisu przez 4 wątki: 1 int x = get_local_id(0); 2 t[0][x] =... nie ma konfliktu

Konflikty przykład Załóżmy, że pamięć podzielona jest na 4 banki Dana jest macierz int t[4][4] 1 // Komórki macierzy 2 t[0][0] t[0][1] t[0][2] t[0][3] 3 t[1][0] t[1][1] t[1][2] t[1][3] 4 t[2][0] t[2][1] t[2][2] t[2][3] 5 t[3][0] t[3][1] t[3][2] t[3][3] 1 // Numery banków komórek 2 0 1 2 3 3 0 1 2 3 4 0 1 2 3 5 0 1 2 3 Przy próbie zapisu przez 4 wątki: 1 int x = get_local_id(0); 2 t[x][0] =... pojawia się konflikt 4-drożny, wszystkie wątki zapisują do banku 0

Konflikty przykład W celu rozwiązania konfliktu można zmienić rozmieszczenie elementów w pamięci, tak aby znalazły się w różnych bankach. 1 int t[4][4 + 1]; // dodatkowy element zmienia rozmieszczenie elementów w 2 // pamięci 1 // Komórki macierzy 2 [0][0] [0][1] [0][2] [0][3] [0][4] 3 [1][0] [1][1] [1][2] [1][3] [1][4] 4 [2][0] [2][1] [2][2] [2][3] [2][4] 5 [3][0] [3][1] [3][2] [3][3] [3][4] 1 // Numery banków komórek 2 0 1 2 3 0 3 1 2 3 0 1 4 2 3 0 1 2 5 3 0 1 2 3

Konflikty przykład W celu rozwiązania konfliktu można zmienić rozmieszczenie elementów w pamięci, tak aby znalazły się w różnych bankach. 1 int t[4][4 + 1]; // dodatkowy element zmienia rozmieszczenie elementów w 2 // pamięci 1 // Komórki macierzy 2 [0][0] [0][1] [0][2] [0][3] [0][4] 3 [1][0] [1][1] [1][2] [1][3] [1][4] 4 [2][0] [2][1] [2][2] [2][3] [2][4] 5 [3][0] [3][1] [3][2] [3][3] [3][4] 1 // Numery banków komórek 2 0 1 2 3 0 3 1 2 3 0 1 4 2 3 0 1 2 5 3 0 1 2 3 Teraz zapis pierwszej kolumny t 1 int x = get_local_id(0); 2 t[x][0] =... będzie dotyczył różnych banków pamięci.

Pamięć współdzielona konflikty W celu wykrycia konfliktów można skorzystać z profilera Nvidia Visual Profiler AMD CodeXL Usunięcie konfliktów wymaga zmiany kolejności operacji na pamięci współdzielonej lub umieszczenia danych w pamięci w taki sposób, by nie zachodził konflikt

Pamięć lokalna w OpenCL uwagi Ze względu na krótki czas dostępu często można uzyskać przyspieszenie obliczeń zapisując dane w pamięci lokalnej grupy (bloku) wątków 1 #define TILE_SIZE 32 2 3 kernel void transpose(int size, 4 global int *A, 5 global int *B) { 6 local int tmp[tile_size][tile_size]; // tablica pomocnicza Problem w tym, że rozmiar tmp musi być znany na etapie kompilacji kernela, a nie jego uruchomienia

Pamięć lokalna w OpenCL uwagi Rozwiązaniem jest przekazanie tablicy w pamięci lokalnej w postaci parametru kernela 1 kernel void transpose(int size, 2 global int *A, 3 global int *B, 4 local int *tmp, 5 int tile_size) { 6 tmp[ y * tile_size + x ] =... Przykład przekazania parametru 1 int tile_size = 32; 2 clsetkernelarg(kernel_id, 3, tile_size * tile_size * sizeof(int), NULL); 3 clsetkernelarg(kernel_id, 4, sizeof(int), &tile_size);

Odwzorowanie Algorytm odwzorowania (ang. map) przekształca pojedynczy element wektora (macierzy itp.) wejściowego na element wektora (macierzy itp.) wynikowego Obliczenia dla poszczególnych elementów są niezależne od siebie

Odwzorowanie Typem odwzorowania jest również szablon (ang. stencil), w którym wartość elementu wynikowego zależy od kilku elementów sąsiednich określonych przez ustalony szablon Obliczenia dla poszczególnych elementów są niezależne od siebie, pomimo że sąsiedztwa nakładają się Rysunek : Przypadek 2D Rysunek : Zależności danych w przypadku danych trójwymiarowych

Redukcja Algorytm redukcji Dane wejściowe uporządkowany zbiór elementów [a 0, a 1,..., a n 1 ] binarny operator, który jest łączny Wynik: (a 0 a 1 a 2... a n 1 )

Redukcja łączność Operator jest łączny w zbiorze S, jeżeli a,b,c S a (b c) = (a b) c

Redukcja łączność Operator jest łączny w zbiorze S, jeżeli a,b,c S a (b c) = (a b) c Przykładowe operatory łączne: suma (a + b) iloczyn (a b) suma logiczna (a b) iloczyn logiczny (a b) minimum min(a, b) maksimum max(a, b) złożenie funkcji f (g h) = (f g) h

Redukcja łączność Operator jest łączny w zbiorze S, jeżeli a,b,c S a (b c) = (a b) c Przykładowe operatory łączne: suma (a + b) iloczyn (a b) suma logiczna (a b) iloczyn logiczny (a b) minimum min(a, b) maksimum max(a, b) złożenie funkcji f (g h) = (f g) h Przykładowe operatory niełączne: różnica (a b) iloraz (a/b) potęgowanie a b

Redukcja sekwencyjnie Algorytm sekwencyjny 1 sum = a[0] 2 for (i = 1; i < n; i++) 3 sum = sum + a[i] 4 return sum Złożoność: n 1 operacji, czyli O(n) Wynik kolejnej iteracji zależy od wyniku poprzednich

Redukcja równolegle a b c d Kolejność wykonania operacji w algorytmie to: ((a b) c) d a b c d

Redukcja równolegle a b c d Kolejność wykonania operacji w algorytmie to: ((a b) c) d a b c Jednak zgodnie z założeniem operator jest łączny, co pozwala na zmianę kolejności wykonania operacji d

Redukcja równolegle a b c d Kolejność wykonania operacji w algorytmie to: ((a b) c) d a b c d Jednak zgodnie z założeniem operator jest łączny, co pozwala na zmianę kolejności wykonania operacji (a b) (c d)

Redukcja równolegle ((a b) c) d (a b) (c d) a b c d a b c d a b a b c d c d 3 operacje 3 kroki (wysokość drzewa) 3 operacje 2 kroki

Redukcja równolegle Data: a[0... n 1] Result: Wartość redukcji w a[n 1] for d from 0 to log 2 n 1 do for i from 0 to n 1 by 2 d+1 do in parallel a[i + 2 d+1 1] a[i + 2 d 1] a[i + 2 d+1 1] end end

Redukcja równolegle Data: a[0... n 1] Result: Wartość redukcji w a[n 1] for d from 0 to log 2 n 1 do for i from 0 to n 1 by 2 d+1 do in parallel a[i + 2 d+1 1] a[i + 2 d 1] a[i + 2 d+1 1] end end Złożoność: O(log n) przy n/2 procesorach

Redukcja OpenCL Wersja dla pojedynczego bloku wątków 1 kernel void reduce(int n, global int *data) { 2 const int tid = get_local_id(0); 3 for (int k = 1; k < n; k *= 2) { 4 if ((tid+1) % (2*k) == 0) { 5 data[ tid ] += data[ tid - k ]; 6 } 7 barrier(clk_local_mem_fence); 8 } 9 // wynik jest w data[n-1] 10 } k = 2 d Sprawdzenie d < log 2 n można zastąpić 2 d < n k < n

Redukcja OpenCL Wersja alternatywna 1 kernel void reduce(int n, global int *data) { 2 const int tid = get_local_id(0); 3 const int group_size = get_local_size(0); 4 for (int k = group_size / 2; k > 0; k /= 2) { 5 if ( tid < k ) { 6 data[ tid ] += data[ tid + k ]; 7 } 8 barrier(clk_local_mem_fence); 9 } 10 // wynik w data[0] 11 } Wersja działa dla tablicy data o długości równej liczbie wątków w grupie roboczej (bloku wątków)

Redukcja OpenCL Wersja alternatywna 1 kernel void reduce(int n, global int *data) { 2 const int tid = get_local_id(0); 3 const int group_size = get_local_size(0); 4 for (int k = group_size / 2; k > 0; k /= 2) { 5 if ( tid < k ) { 6 data[ tid ] += data[ tid + k ]; 7 } 8 barrier(clk_local_mem_fence); 9 } 10 // wynik w data[0] 11 } Wersja działa dla tablicy data o długości równej liczbie wątków w grupie roboczej (bloku wątków) Ponieważ połowa wątków jest nieaktywna od początku, to można zmienić for (int k = group_size / 2; k > 0; k /= 2) na for (int k = group_size; k > 0; k /= 2) i uruchomić algorytm z liczbą wątków w grupie o połowę mniejszą

Redukcja OpenCL Przykład wersja 1 0 [ 3 1 7 0 4 1 6 3 ] 1 [ 3 4 7 7 4 5 6 9 ] 2 [ 3 4 7 11 4 5 6 14 ] 0 [ 3 4 7 11 4 5 6 25 ] Przykład wersja 2 0 [ 3 1 7 0 4 1 6 3 ] 1 [ 7 2 13 3 4 1 6 3 ] 2 [ 20 5 13 3 4 1 6 3 ] 0 [ 25 5 13 3 4 5 6 3 ]

Redukcja OpenCL Liczba wątków w grupie roboczej jest mocno ograniczona Np. dla Nvidia Fermi maks. liczba wątków w grupie to 1024 Daje to możliwość redukcji dla tablic o dł. 2048 W przypadku większych tablic trzeba redukcję przeprowadzać etapami równocześnie dla porcji tablicy o długości dopasowanej do l. wątków w grupie i zapis do tablicy pomocniczej out redukcja na tablicy pomocniczej out być może kolejny etap, jeżeli dł. tablicy out jest większa niż l. wątków w grupie

Redukcja OpenCL Pamięć globalna Pamięć lokalna... Redukcje w grupach wątków

Redukcja OpenCL 1 // Wersja rozszerzona 2 // out[i] = wartość redukcji dla i-tej grupy roboczej 3 kernel void reduce_big(int n, global int *data, global int *out) { 4 const int loc_id = get_local_id(0); 5 const int id = get_group_id(0) * get_local_size(0) * 2 + loc_id; 6 const int group_size = get_local_size(0); 7 for (int k = group_size; k > 0; k /= 2) { 8 if ( loc_id < k ) { 9 data[ id ] += data[ id + k ]; 10 } 11 barrier(clk_local_mem_fence); 12 } 13 if (loc_id == 0) { 14 out[ get_group_id(0) ] = data[ id ]; 15 } 16 }

Redukcja OpenCL 1 // Wersja rozszerzona 2 // out[i] = wartość redukcji dla i-tej grupy roboczej 3 kernel void reduce_big(int n, global int *data, global int *out) { 4 const int loc_id = get_local_id(0); 5 const int id = get_group_id(0) * get_local_size(0) * 2 + loc_id; 6 const int group_size = get_local_size(0); 7 for (int k = group_size; k > 0; k /= 2) { 8 if ( loc_id < k ) { 9 data[ id ] += data[ id + k ]; 10 } 11 barrier(clk_local_mem_fence); 12 } 13 if (loc_id == 0) { 14 out[ get_group_id(0) ] = data[ id ]; 15 } 16 } Obliczenia można by przyspieszyć jeszcze przez: użycie pamięci lokalnej rozwinięcie pętli

Redukcja pamięć lokalna 1 // Wersja z użyciem pamięci lokalnej 2 // out[i] = wartość redukcji dla i-tej grupy roboczej 3 kernel void reduce(int n, global int *data, global int *out, 4 local int *cache) { 5 const int local_id = get_local_id(0); 6 const int group_size = get_local_size(0); 7 const int global_id = get_group_id(0) * group_size * 2 + local_id; 8 9 cache[local_id] = data[global_id]; 10 cache[local_id + group_size] = data[global_id + group_size]; 11 12 barrier(clk_local_mem_fence); 13 for (int k = group_size; k > 0; k /= 2) { 14 if ( local_id < k ) { 15 cache[ local_id ] += cache[ local_id + k ]; 16 } 17 barrier(clk_local_mem_fence); 18 } 19 if (local_id == 0) { 20 out[ get_group_id(0) ] = cache[0]; 21 } 22 }

Redukcja uwagi Przedstawiona wersja wymaga odpowiedniej liczby etapów, aby wyznaczyć redukcję dużej tablicy Pierwszy etap może wymagać uruchomienia znacznej liczby grup, tj. n/(2 group size) Bardziej efektywne jest uruchomienie tylko takiej liczby grup, g, żeby nasycić wszystkie multiprocesory GPU każda grupa wyznaczy wartość redukcji dla dużego fragmentu tablicy, tj. o rozmiarze n/g wynik zapisany w tablicy pomocniczej out może zostać zredukowany sekwencyjnie

Redukcja 1 kernel 2 void reduce( global int *vec, local int *scratch, 3 const int length, global int *result) { 4 int sum = 0; 5 // Sekwencyjna redukcja elementów tablicy oddalonych o global_size 6 for (int i = get_global_id(0); i < length; i+=get_global_size(0)){ 7 sum += vec[i]; 8 } 9 // Równoległa redukcja tablicy scratch 10 int local_index = get_local_id(0); 11 scratch[local_index] = sum; 12 barrier(clk_local_mem_fence); 13 for(int offset = get_local_size(0) / 2; offset > 0; offset /= 2) { 14 if (local_index < offset) { 15 int other = scratch[local_index + offset]; 16 scratch[local_index] += other; 17 } 18 barrier(clk_local_mem_fence); 19 } 20 if (local_index == 0) { 21 result[get_group_id(0)] = scratch[0]; 22 } 23 }

Redukcja złożoność T (n, p) = O(n/p + log n) Wewnątrz grupy wątków p = n, więc złożoność wewnątrz grupy wątków to O(log n) Dla porównania złożoność wersji sekwencyjnej to O(n)

Sumy prefiksowe Sumy prefiksowe (ang. all-prefix-sums, scan) Dane wejściowe uporządkowany zbiór elementów [a 0, a 1,..., a n 1 ] binarny operator, który jest łączny element identycznościowy I Wynik: (a 0, (a 0 a 1 ),..., (a 0 a 1... a n 1 ))

Sumy prefiksowe Sumy prefiksowe (ang. all-prefix-sums, scan) Dane wejściowe uporządkowany zbiór elementów [a 0, a 1,..., a n 1 ] binarny operator, który jest łączny element identycznościowy I Wynik: (a 0, (a 0 a 1 ),..., (a 0 a 1... a n 1 )) 1 Ciąg wejściowy: [ 3 1 7 0 4 1 6 3 ] 2 Sumy prefiksowe: [ 3 4 11 11 15 16 22 25 ]

Sumy prefiksowe Algorytm wyznaczania sum prefiksowych stanowi podstawę wielu algorytmów równoległych, m.in.: sortowania pozycyjnego (ang. radix sort) sortowania szybkiego (ang. quicksort) porównywania ciągów obliczania wielomianów wyznaczania histogramu usuwania zerowych elementów z rzadkiej macierzy / wektora (ang. stream compaction)

Sumy prefiksowe Algorytm sekwencyjny 1 acc = a[0]; 2 out[0] = acc; 3 for (i = 1; i < n; i++) 4 acc = acc op a[i]; // op - operator 5 out[i] = acc; Złożoność: n 1 operacji, czyli O(n)

Sumy prefiksowe równolegle Dwa najbardziej znane algorytmy obliczania sum prefiksowych to: algorytm Hillis-Steele 1 liczba kroków log n liczba operacji O(n log n) algorytm Blellocha 2 liczba kroków 2 log n liczba operacji O(n) 1 Hillis, W. Daniel, and Guy L. Steele Jr. Data parallel algorithms. Communications of the ACM 29.12 (1986): 1170-1183. 2 Blelloch, Guy E. Scans as primitive parallel operations. Computers, IEEE Transactions on 38.11 (1989): 1526-1538.

Sumy prefiksowe Hillis-Steele Rysunek : Ilustracja działania algorytmu sum prefiksowych Hillis-Steele W każdym kroku do elementu o indeksie i dodajemy (lub inny operator) wartość elementu o indeksie i 2 d

Sumy prefiksowe Hillis-Steele Rysunek : Przykład działania alg. Hillis-Steele

Sumy prefiksowe Hillis-Steele algorytm Data: a[0... 1][0... n 1], n = 2 m in = 0; out = 1; for d from 0 to m 1 do for k from 0 to n 1 do in parallel if k 2 d 0 then a[out][k] a[in][k] + a[in][k 2 d ]; else a[out][k] a[in][k]; end swap(in, out); end end

Sumy prefiksowe Hillis-Steele algorytm Data: a[0... 1][0... n 1], n = 2 m in = 0; out = 1; for d from 0 to m 1 do for k from 0 to n 1 do in parallel if k 2 d 0 then a[out][k] a[in][k] + a[in][k 2 d ]; else a[out][k] a[in][k]; end swap(in, out); end end Jak widać, algorytm ten nie działa w miejscu wymaga dodatkowego wektora o dł. O(n) w roli bufora

Sumy prefiksowe Hillis-Steele kernel 1 kernel void scan( global float *in_data, 2 int n, 3 global float *result, 4 local float *temp) { 5 int global_id = get_global_id(0); 6 int local_id = get_local_id(0); 7 int group_size = get_local_size(0); 8 int in = 0; 9 int out = 1; 10 temp[local_id] = in_data[global_id]; 11 barrier(clk_local_mem_fence); 12 for(uint s = 1; s < group_size; s = s*2) { 13 if(local_id > (s-1)) { 14 temp[out * n + local_id] += temp[in * n + local_id - s]; 15 } else { 16 temp[out * n + local_id] = temp[in * n + local_id]; 17 } 18 barrier(clk_local_mem_fence); 19 in = 1 - in; out = 1 - out; 20 } 21 result[global_id] = temp[out * n + local_id]; 22 }

Sumy prefiksowe Hillis-Steele złożoność Liczba operacji: (2 m 2 0 ) + (2 m 2 1 ) +... + (2 m 2 m 1 ) = = m 2 m (2 0 + 2 1 +... + 2 m 1 ) = m 2 m 2 m + 1 = 2 m (m 1) + 1 = n(log(n) 1) + 1 czyli złożoność O(n log n), więcej niż optymalna złożoność O(n) dla danych rozmiaru n = 10 6 wykona około 20x więcej obliczeń

Sumy prefiksowe alg. Blellocha Sumy prefiksowe (scan) ciągu [a 0, a 1,..., a n 1 ] można wyznaczyć na podstawie wyłącznych sum prefiksowych (prescan) ciągu [I, a 0, a 1,..., a n 2 ], gdzie I to element identycznościowy (zero) Wyznaczanie sum prefiksowych odbywa się w 2 fazach: up-sweep równoznaczny z prezentowanym poprzednio algorytmem redukcji down-sweep wyznaczanie sum prefiksowych ciągu [I, a 0, a 1,..., a n 2 ]

Sumy prefiksowe przykład 1 Ciąg wejściowy: [ 3 1 7 0 4 1 6 3 ] 2 Sumy prefiksowe: [ 3 4 11 11 15 16 22 25 ] 3 Sumy prefiksowe wyłączne:[ 0 3 4 11 11 15 16 22 ]

Sumy prefiksowe up-sweep 25 11 14 4 7 5 9 3 1 7 0 4 1 6 3 sum[v] = sum[l[v]] + sum[r[v]]

Sumy prefiksowe up-sweep + down-sweep 25 11 14 4 7 5 9 3 1 7 0 4 1 6 3 sum[v] = sum[l[v]] + sum[r[v]] 25 0 + 11 0 11 14 11 40 74 511 916 30 13 73 011 411 15 616 322 prescan[l[v]] = prescan[v] prescan[r[v]] = sum[l[v]] + prescan[v]

Algorytm down-sweep 25 0 11 0 14 11 Drzewo 40 74 511 916 30 13 73 011 411 15 616 322 Wektor 30 13 73 011 411 15 616 322 0 1 2 3 4 5 6 7 Zależy nam na ostatnim poziomie drzewa, dlatego wystarczy wektor o długości n.

Algorytm Blellocha przykład

Sumy prefiksowe alg. Blellocha down-sweep Data: a[0... n 1] a[n 1] 0; for d from (log n) 1 downto 0 do for i from 0 to n 1 by 2 d+1 do in parallel t a[i + 2 d 1] a[i + 2 d 1] a[i + 2 d+1 1] a[i + 2 d+1 1] t + a[i + 2 d 1] a[i + 2 d+1 1] end end

Sumy prefiksowe alg. Blellocha down-sweep Data: a[0... n 1] a[n 1] 0; for d from (log n) 1 downto 0 do for i from 0 to n 1 by 2 d+1 do in parallel t a[i + 2 d 1] a[i + 2 d 1] a[i + 2 d+1 1] a[i + 2 d+1 1] t + a[i + 2 d 1] a[i + 2 d+1 1] end end Złożoność: O(log n) przy n procesorach

Algorytm Blellocha kernel 1 kernel 2 void prescan( global float *data, int n, 3 local int* temp, global float *result) { 4 int local_id = get_local_id(0); 5 int offset = 1; 6 temp[2*local_id] = data[2*local_id]; // do pam. lokalnej 7 temp[2*local_id+1] = data[2*local_id+1]; 8 for (int d = n / 2; d > 0; d /= 2) { // sumuj w górę drzewa (upsweep) 9 barrier(clk_local_mem_fence); 10 if (local_id < d) { 11 int ai = offset*(2*local_id+1)-1; 12 int bi = offset*(2*local_id+2)-1; 13 temp[bi] += temp[ai]; 14 } 15 offset *= 2; 16 } 17 if (local_id == 0) { temp[n - 1] = 0; } // korzeń drzewa -> 0

Algorytm Blellocha kernel 1 // c.d. 2 for (int d = 1; d < n; d *= 2) { // sumuj w dół drzewa (down-sweep) 3 offset /= 2; 4 barrier(clk_local_mem_fence); 5 if (local_id < d) { 6 int ai = offset*(2*local_id+1)-1; 7 int bi = offset*(2*local_id+2)-1; 8 float t = temp[ai]; 9 temp[ai] = temp[bi]; 10 temp[bi] += t; 11 } 12 } 13 barrier(clk_local_mem_fence); 14 result[2*local_id] = temp[2*local_id]; // zapisz wyniki 15 result[2*local_id+1] = temp[2*local_id+1]; 16 }

Algorytm Blellocha kernel konflikty W przypadku, gdy kilka wątków zapisuje/odczytuje dane w pamięci lokalnej umieszczone pod różnymi adresami, ale w tym samym banku pamięci następuje konflikt Nie ma problemu, gdy wszystkie wątki w ramach osnowy (warp/wavefront) zapisują/odczytują dane umieszczone w różnych bankach zapisują/odczytują daną pod tym samym adresem W przedstawionym algorytmie również występują konflikty w dostępie do pamięci, które mogą mieć wpływ na czas działania w zależności od architektury GPU

Algorytm Blellocha kernel konflikty Przykładowo dla fragmentu: 1 temp[2*local_id] = data[2*local_id]; // do pam. lokalnej 2 temp[2*local_id+1] = data[2*local_id+1]; Wątek 0 odczytuje z banków 0 i 1 Wątek 1 odczytuje z banków 2 i 3... Wątek 8 odczytuje z banków 16 i 17, ale gdy banków jest tylko 16, to daje 0 i 1 ponownie konflikt dwudrożny

Algorytm Blellocha kernel konflikty Rysunek : Ilustracja konfliktów dostępu do pam. lokalnej autor Mark Harris

Algorytm Blellocha kernel konflikty Banks accessed Thread id: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Conflicts: -------------------------------------------------------------------------------------- Step 0: 0 2 4 6 8 10 12 14 0 2 4 6 8 10 12 14 --> 2-way Step 1: 1 3 5 7 9 11 13 15 1 3 5 7 9 11 13 15 --> 2-way Step 2: 0 2 4 6 8 10 12 14 0 2 4 6 8 10 12 14 --> 2-way Step 3: 1 3 5 7 9 11 13 15 1 3 5 7 9 11 13 15 --> 2-way Step 4: 1 5 9 13 1 5 9 13 1 5 9 13 1 5 9 13 --> 4-way Step 5: 3 7 11 15 3 7 11 15 3 7 11 15 3 7 11 15 --> 4-way Step 6: 3 11 3 11 3 11 3 11 3 11 3 11 3 11 3 11 --> 8-way Step 7: 7 15 7 15 7 15 7 15 7 15 7 15 7 15 7 15 --> 8-way Step 8: 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 --> 16-way Step 9: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 --> 16-way Step 10: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 --> 16-way Step 11: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 --> 16-way Step 12: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 --> 16-way Step 13: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 --> 16-way Step 14: 15 15 15 15 15 15 15 15 - - - - - - - - --> 8-way Step 15: 15 15 15 15 15 15 15 15 - - - - - - - - --> 8-way Step 16: 15 15 15 15 - - - - - - - - - - - - --> 4-way Step 17: 15 15 15 15 - - - - - - - - - - - - --> 4-way Step 18: 15 15 - - - - - - - - - - - - - - --> 2-way Step 19: 15 15 - - - - - - - - - - - - - - --> 2-way Step 20: 15 - - - - - - - - - - - - - - - --> - Step 21: 15 - - - - - - - - - - - - - - - --> - Analiza konfliktów dla pierwszej części alg. Blellocha (up-sweep) rozm. tablicy 1024, rozm. grupy wątków 512

Algorytm Blellocha kernel konflikty Banks accessed Thread id: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Conflicts: -------------------------------------------------------------------------------------- Step 0: 15 - - - - - - - - - - - - - - - --> - Step 1: 15 - - - - - - - - - - - - - - - --> - Step 2: 15 15 - - - - - - - - - - - - - - --> 2-way Step 3: 15 15 - - - - - - - - - - - - - - --> 2-way Step 4: 15 15 15 15 - - - - - - - - - - - - --> 4-way Step 5: 15 15 15 15 - - - - - - - - - - - - --> 4-way Step 6: 15 15 15 15 15 15 15 15 - - - - - - - - --> 8-way Step 7: 15 15 15 15 15 15 15 15 - - - - - - - - --> 8-way Step 8: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 --> 16-way Step 9: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 --> 16-way Step 10: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 --> 16-way Step 11: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 --> 16-way Step 12: 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 --> 16-way Step 13: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 --> 16-way Step 14: 3 11 3 11 3 11 3 11 3 11 3 11 3 11 3 11 --> 8-way Step 15: 7 15 7 15 7 15 7 15 7 15 7 15 7 15 7 15 --> 8-way Step 16: 1 5 9 13 1 5 9 13 1 5 9 13 1 5 9 13 --> 4-way Step 17: 3 7 11 15 3 7 11 15 3 7 11 15 3 7 11 15 --> 4-way Step 18: 0 2 4 6 8 10 12 14 0 2 4 6 8 10 12 14 --> 2-way Step 19: 1 3 5 7 9 11 13 15 1 3 5 7 9 11 13 15 --> 2-way Analiza konfliktów dla drugiej części alg. Blellocha (down-sweep) rozm. tablicy 1024, rozm. grupy wątków 512

Rysunek : Rozwiązanie części konfliktów dostępu do pam. lokalnej dzięki przesunięciu kolejnych segmentów elementów w tablicy pomocniczej autor Mark Harris Algorytm Blellocha kernel konflikty

Algorytm Blellocha kernel konflikty Banks accessed Thread id: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Conflicts: -------------------------------------------------------------------------------------- Step 0: 0 2 4 6 8 10 12 14 1 3 5 7 9 11 13 15 --> - Step 1: 1 3 5 7 9 11 13 15 2 4 6 8 10 12 14 0 --> - Step 2: 0 2 4 6 8 10 12 14 1 3 5 7 9 11 13 15 --> - Step 3: 1 3 5 7 9 11 13 15 2 4 6 8 10 12 14 0 --> - Step 4: 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 0 --> - Step 5: 3 7 11 15 4 8 12 0 5 9 13 1 6 10 14 2 --> - Step 6: 3 11 4 12 5 13 6 14 7 15 8 0 9 1 10 2 --> - Step 7: 7 15 8 0 9 1 10 2 11 3 12 4 13 5 14 6 --> - Step 8: 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 --> - Step 9: 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 --> - Step 10: 15 1 3 5 7 9 11 13 15 1 3 5 7 9 11 13 --> 2-way Step 11: 0 2 4 6 8 10 12 14 0 2 4 6 8 10 12 14 --> 2-way Step 12: 0 4 8 12 0 4 8 12 0 4 8 12 0 4 8 12 --> 4-way Step 13: 2 6 10 14 2 6 10 14 2 6 10 14 2 6 10 14 --> 4-way Step 14: 2 10 2 10 2 10 2 10 - - - - - - - - --> 4-way Step 15: 6 14 6 14 6 14 6 14 - - - - - - - - --> 4-way Step 16: 6 6 6 6 - - - - - - - - - - - - --> 4-way Step 17: 14 14 14 14 - - - - - - - - - - - - --> 4-way Step 18: 14 14 - - - - - - - - - - - - - - --> 2-way Step 19: 14 14 - - - - - - - - - - - - - - --> 2-way Step 20: 14 - - - - - - - - - - - - - - - --> - Step 21: 14 - - - - - - - - - - - - - - - --> - Zmiana ta nie usuwa wszystkich konfliktów, lecz je ogranicza Niestety kosztem dodatkowych obliczeń związanych z wyznaczaniem indeksu elementu index = index + index / 16

Algorytm Blellocha kernel konflikty Banks accessed (up-sweep) Thread id: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Conflicts: -------------------------------------------------------------------------------------- Step 0: 0 2 4 6 8 10 12 14 1 3 5 7 9 11 13 15 --> - Step 1: 1 3 5 7 9 11 13 15 2 4 6 8 10 12 14 0 --> - Step 2: 0 2 4 6 8 10 12 14 1 3 5 7 9 11 13 15 --> - Step 3: 1 3 5 7 9 11 13 15 2 4 6 8 10 12 14 0 --> - Step 4: 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 0 --> - Step 5: 3 7 11 15 4 8 12 0 5 9 13 1 6 10 14 2 --> - Step 6: 3 11 4 12 5 13 6 14 7 15 8 0 9 1 10 2 --> - Step 7: 7 15 8 0 9 1 10 2 11 3 12 4 13 5 14 6 --> - Step 8: 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 --> - Step 9: 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 --> - Step 10: 15 1 3 5 7 9 11 13 0 2 4 6 8 10 12 14 --> - Step 11: 0 2 4 6 8 10 12 14 1 3 5 7 9 11 13 15 --> - Step 12: 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 15 --> - Step 13: 2 6 10 14 3 7 11 15 4 8 12 0 5 9 13 1 --> - Step 14: 2 10 3 11 4 12 5 13 - - - - - - - - --> - Step 15: 6 14 7 15 8 0 9 1 - - - - - - - - --> - Step 16: 6 7 8 9 - - - - - - - - - - - - --> - Step 17: 14 15 0 1 - - - - - - - - - - - - --> - Step 18: 14 0 - - - - - - - - - - - - - - --> - Step 19: 15 1 - - - - - - - - - - - - - - --> - Step 20: 15 - - - - - - - - - - - - - - - --> - Step 21: 1 - - - - - - - - - - - - - - - --> - Konflikty można wyeliminować całkowicie, jeżeli dodamy dodatkowe przesunięcie co 16 porcji po 16 elementów index = index + index / 16 + index / 256 Niestety dodatkowy koszt dla każdego indeksu nieopłacalne

Algorytm Blellocha kernel konflikty Banks accessed (down-sweep) Thread id: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Conflicts: -------------------------------------------------------------------------------------- Step 0: 15 - - - - - - - - - - - - - - - --> - Step 1: 1 - - - - - - - - - - - - - - - --> - Step 2: 14 0 - - - - - - - - - - - - - - --> - Step 3: 15 1 - - - - - - - - - - - - - - --> - Step 4: 6 7 8 9 - - - - - - - - - - - - --> - Step 5: 14 15 0 1 - - - - - - - - - - - - --> - Step 6: 2 10 3 11 4 12 5 13 - - - - - - - - --> - Step 7: 6 14 7 15 8 0 9 1 - - - - - - - - --> - Step 8: 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 15 --> - Step 9: 2 6 10 14 3 7 11 15 4 8 12 0 5 9 13 1 --> - Step 10: 15 1 3 5 7 9 11 13 0 2 4 6 8 10 12 14 --> - Step 11: 0 2 4 6 8 10 12 14 1 3 5 7 9 11 13 15 --> - Step 12: 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 --> - Step 13: 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 --> - Step 14: 3 11 4 12 5 13 6 14 7 15 8 0 9 1 10 2 --> - Step 15: 7 15 8 0 9 1 10 2 11 3 12 4 13 5 14 6 --> - Step 16: 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 0 --> - Step 17: 3 7 11 15 4 8 12 0 5 9 13 1 6 10 14 2 --> - Step 18: 0 2 4 6 8 10 12 14 1 3 5 7 9 11 13 15 --> - Step 19: 1 3 5 7 9 11 13 15 2 4 6 8 10 12 14 0 --> - W przyszłości rozwiązania sprzętowe zapewne zniwelują opóźnienia związane z dostępem do pamięci lokalnej wynikające z konfliktów

Algorytm Blellocha dowolny rozmiar tablicy Dla prezentowanego algorytmu przyjęto założenie wykonania przez jeden multiprocesor a więc rozmiar tablicy zależny od maks. liczby wątków w grupie roboczej, np. 2 1024 Dla tablic o większych rozmiarach sumy prefiksowe należy wyznaczyć w dwóch etapach n-elementową tablicę wejściową dzielimy na n/b bloków, gdzie b/2 to liczba wątków w grupie ostatni element sum prefiksowych zapisywany jest do tablicy pomocniczej sums wyznaczamy sumy prefiksowe dla tablicy sums i na tej podstawie wyznaczamy właściwe sumy prefiksowe dla tablicy wejściowej dodając do każdego elementu bloku i wartość sums[i]

Algorytm Blellocha dowolny rozmiar tablicy

Algorytm Blellocha wydajność Rysunek : Porównanie wydajności obliczania sum prefiksowych (Harris, Mark, Shubhabrata Sengupta, and John D. Owens. Gpu gems 3. Parallel Prefix Sum (Scan) with CUDA (2007): 851-876.)

Algorytm Blellocha wydajność # elements CPU Scan (ms) GPU Scan (ms) Speedup 1024 0.002231 0.079492 0.03 32768 0.072663 0.106159 0.68 65536 0.146326 0.137006 1.07 131072 0.726429 0.200257 3.63 262144 1.454742 0.326900 4.45 524288 2.911067 0.624104 4.66 1048576 5.900097 1.118091 5.28 2097152 11.848376 2.099666 5.64 4194304 23.835931 4.062923 5.87 8388688 47.390906 7.987311 5.93 16777216 94.794598 15.854781 5.98

Zastosowania sum prefiksowych zliczanie Dane wej.: n-elementowa tablica wartości logicznych ( true, false ) Wynik: n-elementowa tablica, w której i-ty element ma wartość równą liczbie wartości true na lewo od niego Wejście T F T T F T Wynik 0 1 1 2 3 3

Zastosowania sum prefiksowych podział Algorytm podziału (ang. split) wektora Cel: podział wektora wejściowego na dwie części elementy oznaczone jako false (0) po lewej stronie elementy oznaczone jako true (1) po prawej stronie Przykład: A B C D E F G Powiązane dane 1 0 1 0 0 1 0 Wektor wejściowy A 0 1 0 1 1 0 1 Negacja wektora wejściowego Ā 0 0 1 1 2 3 3 S sumy prefiksowe (wyłączne) f = 4 Liczba wart. false 0 1 2 3 4 5 6 I identyfikatory wątków 4 5 5 6 6 6 7 T [i] = I [i] S[i] + f 4 0 5 1 2 6 3 Indeksy elementów w tablicy po podziale B D E G A C F Dane po permutacji

Zastosowania sum prefiksowych sortowanie pozycyjne Przykład zastosowania algorytmu podziału do implementacji sortowania pozycyjnego (ang. radix-sort) 3 3 Blelloch, Guy E. Prefix sums and their applications. (1990).

Zastosowania sum prefiksowych sortowanie pozycyjne Alg. podziału ma złożoność T (n, p) = O(n/p + log p) Jeżeli liczby mają O(log n) bitów to potrzeba wykonania log n razy alg. podziału Zatem finalna złożoność sortowania pozycyjnego to: T (n, p) = O( n p log n + log n log p) Złożoność alg. wykonywanego przez grupę p wątków, przy zał. n = p daje złożoność T (n, n) = O(log n + 2 log n) = O(log n)

Zastosowania sum prefiksowych sortowanie szybkie Sumy prefiksowe mogą zostać użyte jako element składowy równoległej implementacji sortowania szybkiego (ang. quicksort) Średnia złożoność obliczeniowa algorytmu to: T (n, p) = O(log n T S (n, p)), gdzie T S (n, p) jest złożonością algorytmu sum prefiksowych Łącznie, średnia złożoność T (n, p) = O( n p log n + log2 n)