Programowanie Równoległe wykład 12 Thrust C++ 30.01.2013 Maciej Matyka Instytut Fizyki Teoretycznej
Co to jest Thrust C++? Thrust C++ biblioteka szablonów Interfejs w pewnym sensie spójny z STL Biblioteka wysokiego poziomu Cel: maksymalnie ułatwić programowanie GPU
Instalacja i kompilacja W CUDA > 4.0 Thrust jest w standardzie! #include <thrust/version.h> #include <iostream> int main(void) { int major = THRUST_MAJOR_VERSION; int minor = THRUST_MINOR_VERSION; std::cout << "Thrust v" << major << "." << minor << std::endl; } return 0; http://code.google.com/p/thrust/wiki/quickstartguide
Podstawy W kodzie C++ używamy przestrzeni nazw Thrust:: Najprostsze kontenery wektory w pamięci urządzenia (device) i w pamięci cpu (host) Przykłady Wektor 100 int w pamięci CPU thrust::host_vector<int> h_vec(100); Wektor 150 double w pamięci GPU thrust::device_vector<double> d_vec(150);
Kopiowanie Kopiowanie przy pomocy operatora = CPU->GPU GPU->CPU CPU->CPU GPU->GPU Przykład: thrust::host_vector<int> h_vec(100); thrust::device_vector<int> d_vec2, d_vec3; d_vec2 = h_vec; d_vec3 = d_vec2; Można też np. tak: thrust::copy (h_vec.begin(), h_vec.end(),d_vec2.begin());
Hello world #include <thrust/host_vector.h> #include <thrust/device_vector.h> #include <iostream> int main (void) { thrust::host_vector <int> h_vec(1<<3); thrust::generate(h_vec.begin(),h_vec.end(), rand); thrust::device_vector<int> d_vec = h_vec; for (int i = 0; i < h_vec.size(); i++) std::cout << h_vec[i] << " " << d_vec[i] << std::endl; } return 0;
Hello world #include <thrust/host_vector.h> #include <thrust/device_vector.h> #include <iostream> int main (void) { thrust::host_vector <int> h_vec(1<<3); thrust::generate(h_vec.begin(),h_vec.end(), rand); thrust::device_vector<int> d_vec = h_vec; for (int i = 0; i < h_vec.size(); i++) std::cout << h_vec[i] << " " << d_vec[i] << std::endl; } return 0;
Hello world #include <thrust/host_vector.h> #include <thrust/device_vector.h> #include <iostream> int main (void) { STL! thrust::host_vector <int> h_vec(1<<3); thrust::generate(h_vec.begin(),h_vec.end(), rand); thrust::device_vector<int> d_vec = h_vec; for (int i = 0; i < h_vec.size(); i++) std::cout << h_vec[i] << " " << d_vec[i] << std::endl; } return 0;
Hello world #include <thrust/host_vector.h> #include <thrust/device_vector.h> #include <iostream> int main (void) { thrust::host_vector <int> h_vec(1<<3); thrust::generate(h_vec.begin(),h_vec.end(), rand); thrust::device_vector<int> d_vec = h_vec; for (int i = 0; i < h_vec.size(); i++) std::cout << h_vec[i] << " " << d_vec[i] << std::endl; } return 0;
Hello world #include <thrust/host_vector.h> #include <thrust/device_vector.h> #include <iostream> int main (void) { thrust::host_vector <int> h_vec(1<<3); thrust::generate(h_vec.begin(),h_vec.end(), rand); thrust::device_vector<int> d_vec = h_vec; for (int i = 0; i < h_vec.size(); i++) std::cout << h_vec[i] << " " << d_vec[i] << std::endl; } return 0; i/o z GPU (kopia, ale wygodne!)
Iteratory Iteratory kontenerów STL - odpowiadniki wskaźników (ale niosą więcej informacji) h_vec.begin(), d_vec.end() etc. zwracają iteratory W Thrust w zależności od umiejscowienia (GPU i CPU) iteratory mają różne typy Na poziomie kompilacji wstawiane są funkcje pracujące na odpowiednich typach (static dispatching)
Praca z własnymi danymi Można mapować wskaźnik na pamięć urządzenia na kontener Thrust Alokacja pamięci w CUDA: int N = 10; int *raw_ptr; cudamalloc (( void **) & raw_ptr, N * sizeof ( int )); Mapowanie adresu + wywołanie funkcji Thrust thrust::device_ptr <int > dev_ptr ( raw_ptr ); thrust::fill ( dev_ptr, dev_ptr + N, ( int ) 0);
Praca z własnymi danymi Można zmapować wskaźnik na pamięć urządzenia na kontener Thrust Alokacja pamięci w CUDA: int N = 10; int *raw_ptr; cudamalloc (( void **) & raw_ptr, N * sizeof ( int )); Mapowanie adresu + wywołanie funkcji Thrust thrust::device_ptr <int> dev_ptr ( raw_ptr ); thrust::fill ( dev_ptr, dev_ptr + N, ( int ) 0);
Niedostatki Thrust Thrust nie implementuje pełnego STL na GPU Na przykład brak kontenera std::list Ale, możemy użyć std::list na CPU do inicjalizacji wektora na GPU Znów przykład z dokumentacji:
std::list -> thrust::device_vector #include <thrust/device_vector.h> #include <thrust/copy.h> #include <list> #include <vector> int main (void) { std::list <int> stl_list ; stl_list.push_back (10) ; stl_list.push_back (20) ; stl_list.push_back (30) ; stl_list.push_back (40) ; thrust::device_vector <int> D(stl_list.begin(), stl_list.end()); std::vector <int> stl_vector (D. size ()); thrust::copy(d.begin(), D.end(), stl_vector.begin()); } return 0;
Algorytmy Wiele z algorytmów Thrust jest odpowiednikiem STL Przykład: thrust::sort i std::sort Wszystkie algorytmy Thrust są dostarczone w wersjach CPU i GPU (static dispatching) thrust::device_vector <int> X(10); thrust::device_vector <int> Y(10); thrust::device_vector <int> Z(10); // Y = X % Z thrust::transform(x.begin(), X.end(), Z.begin(), Y.begin(), thrust::modulus<int>() ); // print Y thrust::copy(y.begin(), Y.end(), std::ostream_iterator<int>( std::cout, "\n")); (Przypisanie do std::ostream_iterator wypisuje elementy z opcjonalnym delimiterem)
Dostępne operacje struct thrust::plus< T > struct thrust::minus< T > struct thrust::multiplies< T > struct thrust::divides< T > struct thrust::modulus< T > struct thrust::negate< T > Przykład z dokumentacji: ( http://wiki.thrust.googlecode.com/hg/html/index.html ) Uwaga: - nie zawsze efektywne, - niekiedy lepiej zaimplementować własną operację
Zdefiniowanie funktora Efektywne wykonanie operacji SAXPY (BLAS) y <- a * x + y x wektor y - wektor a liczba
Zdefiniowanie funktora Struktura funktora: struct saxpy_functor { const float a; saxpy_functor ( float _a) : a(_a) {} host device float operator() (const float & x, const float &y) const { return a * x + y; } }; Wywołanie: void saxpy_fast (float A, thrust::device_vector <float > &X, thrust::device_vector<float> &Y) { // Y <- A * X + Y thrust::transform (X.begin(), X.end(), Y.begin(), Y.begin(), saxpy_functor(a)); }
Zdefiniowanie funktora Struktura funktora: struct saxpy_functor { const float a; saxpy_functor ( float _a) : a(_a) {} host device float operator() (const float & x, const float &y) const { return a * x + y; } }; Wywołanie: void saxpy_fast (float A, thrust::device_vector <float > &X, thrust::device_vector<float> &Y) { // Y <- A * X + Y thrust::transform (X.begin(), X.end(), Y.begin(), Y.begin(), saxpy_functor(a)); }
Redukcja (suma) Wykonuje redukcję (tu suma) na wektorze od first do last init wartość początkowa (prawie zawsze 0) #include <thrust/reduce.h>... int data[6] = {1, 0, 2, 2, 1, 3}; int result = thrust::reduce(data, data + 6, 1); // result == 10
Redukcja (ogólnie) binary_op funkcja binarna #include <thrust/reduce.h> #include <thrust/functional.h>... int data[6] = {1, 0, 2, 2, 1, 3}; int result = thrust::reduce(data, data + 6, -1, thrust::maximum<int>()); // result == 3
Operacje redukcji Inne operacje redukcji na wektorach Thrust thrust::count_if, thrust::min_element, thrust::max_element, thrust::is_sorted, thrust::inner_product Przykład: iloczyn skalarny #include <thrust/inner_product.h>... float vec1[3] = {-1.0f, 12.0f, 1.0f}; float vec2[3] = {1.0f, 2.0f, 3.0f}; float result = thrust::inner_product(vec1, vec1 + 3, vec2, 0.0f); // 26.0f Uwaga: kolejność dodawań nie musi być zachowana (inaczej niż w stl::inner_product)
Szybki przegląd algorytmów Sumy cząstkowe (prefix-sums, scan) thrust::inclusive_scan Operacje grupowania Kopiowanie, usuwanie, przegrupowanie w zależności od wyniku testów (predykaty) np. thrust::copy_if Sortowanie thrust::sort, thrust::stable_sort, thrust::sort_by_key
Koniec Dziękuję za uwagę
Literatura Wykład na podstawie: CUDA Toolkit 4.0 Thrust Quick Start Guide (2011)
Iteratory Thrust (oparte o BoostC+ +) Iteratory dostępu do kontenera Thrust constant_iterator counting_iterator transform_iterator permutation_iterator zip_iterator
Ćwiczenia 1. Zdefiniuj prostą operację na wektorach (może być z BLAS: http://en.wikipedia.org/wiki/basic_linear_algebra_subprograms) 2. Zaimplementuj wersję korzystającą z funktorów wbudowanych Thrust 3. Zaimplementuj własny funktor realizujący ww zadanie 4. Zbadaj wydajność własnego funktora (czas w funkcji rozmiaru problemu) względem operacji wbudowanych Thrust. 5. (*) Dla zainteresowanych porównaj wydajność do wydajności analogicznej funkcji w bibliotece CUBLAS