Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Parallel STL

4,924 views

Published on

Talk about Parallelism in C++17

Published in: Software
  • Be the first to comment

  • Be the first to like this

Parallel STL

  1. 1. C++17: параллельная версия стандартных алгоритмов Евгений Крутько e.s.krutko@gmail.com Национальный исследовательский центр "Курчатовский институт" 25.02.2017
  2. 2. Эволюция многопоточной работы в C++ Нам нужно выполнить работу асинхронно 1 int main(){ 2 //Here we should use some features to call doWork in parallel 3 doWork(int a); 4 doSomeOtherWork(double t); 5 //Ensure doWork completed 6 return 0; 7 }
  3. 3. Давным-давно... ... у нас не было даже стандарта C++11. 1 //Using POSIX API 2 void* makeDoWork(void *arg) { 3 int *a=reinterpret_cast<int*>(arg); 4 doWork(*a); 5 } 6 int main(){ 7 pthread_t thread; 8 int value = 42; 9 pthread_create( 10 &thread, 11 nullptr, 12 makeDoWork, 13 &value); 14 doSomeOtherWork(double t); 15 pthread_join(thread, nullptr); 16 return 0; 17 }
  4. 4. Давным-давно... ... у нас не было даже стандарта C++11. 1 //Using Win API 2 DWORD WINAPI makeDoWork(void *arg) { 3 int *a=reinterpret_cast<int*>(arg); 4 doWork(*a); 5 } 6 int main(){ 7 HANDLE thread; 8 int value = 42; 9 thread = CreateThread( 10 NULL, 11 0, 12 makeDoWork, 13 &value, 14 0, 15 NULL 16 ); 17 doSomeOtherWork(double t); 18 WaitForSingleObject(thread, INFINITE); 19 return 0; 20 }
  5. 5. Кросс-платформенный подход И другие
  6. 6. Начиная со стандарта C++11 std::thread std::async / std::future 1 int main(int argc, char**argv) { 2 std::thread thread; 3 int value; 4 //Do doWork() in new thread 5 thread = std::thread( 6 doWork, 7 std::ref(value)); 8 //Do someything else in this thread 9 doSomeOtherWork(); 10 //Whait for doWork() finishes 11 thread.join(); 12 return 0; 13 }
  7. 7. Начиная со стандарта C++11 std::thread std::async / std::future 1 int main(int argc, char**argv) { 2 int value; 3 //Do doWork() in new thread 4 auto future = std::async( 5 std::launch::async; 6 doWork, 7 std::ref(value)); 8 //Do someything else in this thread 9 doSomeOtherWork(); 10 //Whait for doWork() finishes 11 future.get(); 12 return 0; 13 }
  8. 8. Нам и этого мало, хочется еще проще Возможно когда-нибудь :) 1 auto auto(auto auto) { auto; }
  9. 9. Нам и этого мало, хочется еще проще Возможно когда-нибудь :) 1 auto auto(auto auto) { auto; } Уже /почти/ в стандарте 1 //Something from <algorythm> 2 std::some_standard_algorythm_with_stl_containers( 3 std::begin(container), 4 std::end(container) 5 ); 6 //The same but with specification of execution policy 7 std::some_standard_algorythm_with_stl_containers( 8 ExecutionPolicy policy, 9 std::begin(container), 10 std::end(container) 11 );
  10. 10. Алгоритмы стандартной библиотеки, доступные в параллельном режиме Объявлены в заголовочном файле <algorithm> Пока в стадии TS в заголовочном файле <experimental/algorithm> adjacent_difference adjacent_find all_of any_of copy copy_if copy_n count count_if equal exclusive_scan fill fill_n find find_end find_first_of find_if find_if_not for_each for_each_n generate generate_n includes inclusive_scan inner_product inplace_merge is_heap is_heap_until is_partitioned is_sorted is_sorted_until lexicographical_compare max_element merge min_element minmax_element mismatch move none_of nth_element partial_sort partial_sort_copy partition partition_copy reduce remove remove_copy remove_copy_if remove_if replace replace_copy replace_copy_if replace_if reverse reverse_copy rotate rotate_copy search search_n set_difference set_intersection set_symmetric_difference set_union sort stable_partition stable_sort swap_ranges transform transform_exclusive_scan transform_inclusive_scan transform_reduce uninitialized_copy uninitialized_copy_n uninitialized_fill uninitialized_fill_n unique unique_copy
  11. 11. Политики выполнения 1 //Available from <execution_policy> 2 //While in TS stage from <experimental/execution_policy> 3 4 //Plain old sequenced execution 5 constexpr sequential_execution_policy seq{ }; 6 //Parallel execution 7 constexpr parallel_execution_policy par{ }; 8 //Parallel with SIMD instructions 9 constexpr parallel_vector_execution_policy par_vec{ };
  12. 12. Политики выполнения 1 //Available from <execution_policy> 2 //While in TS stage from <experimental/execution_policy> 3 4 //Plain old sequenced execution 5 constexpr sequential_execution_policy seq{ }; 6 //Parallel execution 7 constexpr parallel_execution_policy par{ }; 8 //Parallel with SIMD instructions 9 constexpr parallel_vector_execution_policy par_vec{ }; Стрелять себе по ногам теперь можно еще одним изящным способом 1 int a[] = {0,1}; 2 std::vector<int> v; 3 std::for_each(std::par, 4 std::begin(a), 5 std::end(a), 6 [&](int i) { 7 v.push_back(i*2+1); // Error: data race 8 });
  13. 13. Дайте две! Как же попробовать? Из документа https://isocpp.org/files/papers/P0024R2.html Microsoft MS ParallelSTL page HPX HPX github Codeplay Sycl github HSA HSA for math science page Thibaut Lutz github NVIDIA github http://github.com/eskrut/ParallelSTL.git
  14. 14. Тестовая задача #1 Обычная реализация 1 auto vec = makeShuffledVector(); 2 double baseDuration = 0; 3 4 auto vecToSort = vec; 5 { 6 Stopwatch sw("plain sort"); 7 std::sort(std::begin(vecToSort), std::end(vecToSort)); 8 baseDuration = sw.duration(); 9 } 10 if (! std::is_sorted(std::begin(vecToSort), std::end(vecToSort))) 11 throw std::runtime_error("Failed with plain sort");
  15. 15. Тестовая задача #1 С последовательной политикой 1 //This includes should be from ${T_LUTZ_ROOT}/include 2 #include <numeric> 3 #include <experimental/algorithm> 4 5 /* --- */ 6 7 vecToSort = vec; 8 { 9 Stopwatch sw("seq sort", baseDuration); 10 sort(std::experimental::parallel::seq, 11 std::begin(vecToSort), std::end(vecToSort)); 12 } 13 if (! std::is_sorted(std::begin(vecToSort), std::end(vecToSort))) 14 throw std::runtime_error("Failed with plain sort");
  16. 16. Тестовая задача #1 С параллельной политикой 1 //This includes should be from ${T_LUTZ_ROOT}/include 2 #include <numeric> 3 #include <experimental/algorithm> 4 5 /* --- */ 6 7 vecToSort = vec; 8 { 9 Stopwatch sw("par sort", baseDuration); 10 sort(std::experimental::parallel::par, 11 std::begin(vecToSort), std::end(vecToSort)); 12 } 13 if (! std::is_sorted(std::begin(vecToSort), std::end(vecToSort))) 14 throw std::runtime_error("Failed with plain sort");
  17. 17. Тестовая задача #1 С последовательной политикой 1 #include "hpx/hpx_init.hpp" 2 #include "hpx/hpx.hpp" 3 4 #include "hpx/parallel/numeric.hpp" 5 #include "hpx/parallel/algorithm.hpp" 6 7 /* --- */ 8 9 vecToSort = vec; 10 { 11 Stopwatch sw("seq sort", baseDuration); 12 hpx::parallel::sort(hpx::parallel::seq, 13 std::begin(vecToSort), std::end(vecToSort)); 14 } 15 if (! std::is_sorted(std::begin(vecToSort), std::end(vecToSort))) 16 throw std::runtime_error("Failed with seq sort");
  18. 18. Тестовая задача #1 С параллельной политикой 1 #include "hpx/hpx_init.hpp" 2 #include "hpx/hpx.hpp" 3 4 #include "hpx/parallel/numeric.hpp" 5 #include "hpx/parallel/algorithm.hpp" 6 7 /* --- */ 8 9 vecToSort = vec; 10 { 11 Stopwatch sw("par sort", baseDuration); 12 hpx::parallel::sort(hpx::parallel::par, 13 std::begin(vecToSort), std::end(vecToSort)); 14 } 15 if (! std::is_sorted(std::begin(vecToSort), std::end(vecToSort))) 16 throw std::runtime_error("Failed with par sort");
  19. 19. Тестовая задача #1 С параллельной и векторизованной политикой 1 #include "hpx/hpx_init.hpp" 2 #include "hpx/hpx.hpp" 3 4 #include "hpx/parallel/numeric.hpp" 5 #include "hpx/parallel/algorithm.hpp" 6 7 /* --- */ 8 9 vecToSort = vec; 10 { 11 Stopwatch sw("par_vec sort", baseDuration); 12 hpx::parallel::sort(hpx::parallel::par_vec, 13 std::begin(vecToSort), std::end(vecToSort)); 14 } 15 if (! std::is_sorted(std::begin(vecToSort), std::end(vecToSort))) 16 throw std::runtime_error("Failed with par_vec sort");
  20. 20. Тестовая задача #2 Обычная реализация 1 const size_t numParts = std::thread::hardware_concurrency()*2 > 0 ? 2 std::thread::hardware_concurrency() * 2 : 8; 3 std::list<std::vector<size_t>> listOfVecs; 4 listOfVecs.resize(numParts); 5 std::generate(listOfVecs.begin(), 6 listOfVecs.end(), 7 [&](){ return makeShuffledVector(memoryToAlloc/numParts);} 8 ); 9 double baseDuration = 0; 10 auto list = listOfVecs; 11 { 12 Stopwatch sw("plain for, plain sort"); 13 for(auto &vecToSort : list) 14 std::sort(std::begin(vecToSort), std::end(vecToSort)); 15 baseDuration = sw.duration(); 16 } 17 for(const auto &vecToSort : list) 18 if (! std::is_sorted(std::begin(vecToSort), std::end(vecToSort))) 19 throw std::runtime_error("Failed with plain for, plain sort");
  21. 21. Тестовая задача #2 Последовательный for, параллельный sort 1 for(auto &vecToSort : list) 2 sort(std::experimental::parallel::par, 3 std::begin(vecToSort), std::end(vecToSort)); 4 5 /* --- */ 6 7 for(auto &vecToSort : list) 8 hpx::parallel::sort(hpx::parallel::par, 9 std::begin(vecToSort), 10 std::end(vecToSort));
  22. 22. Тестовая задача #2 Параллельный for, последовательный sort 1 for_each(std::experimental::parallel::par, 2 std::begin(list), std::end(list), 3 [](std::vector<size_t> &vecToSort){ 4 std::sort(std::begin(vecToSort), std::end(vecToSort)); 5 }); 6 7 /* --- */ 8 9 hpx::parallel::for_each(hpx::parallel::par, 10 std::begin(list), std::end(list), 11 [](std::vector<size_t> &vecToSort){ 12 std::sort(std::begin(vecToSort), std::end(vecToSort)); 13 });
  23. 23. Тестовая задача #2 Параллельный for, параллельный sort [параллельный в квадрате, квадратно параллельный] 1 for_each(std::experimental::parallel::par, 2 std::begin(list), std::end(list), 3 [](std::vector<size_t> &vecToSort){ 4 sort(std::experimental::parallel::par, 5 std::begin(vecToSort), std::end(vecToSort)); 6 }); 7 8 /* --- */ 9 10 hpx::parallel::for_each(hpx::parallel::par, 11 std::begin(list), std::end(list), 12 [](std::vector<size_t> &vecToSort){ 13 hpx::parallel::sort(hpx::parallel::par, 14 std::begin(vecToSort), std::end(vecToSort)); 15 });
  24. 24. Результаты [2 физических, 4 h-threading ядра] Тест #1 Реализация Политика Ускорение t-lutz seq 1 par 2.37 HPX seq 0.99 par 2.61 par_vec 2.64 Тест #2 Реализация Политика for Политика sort Ускорение t-lutz seq par 2.26 par seq 2.78 par par 2.48 HPX seq par 2.52 par seq 2.75 par par 2.87
  25. 25. Race! Догнать и перегнать! 1 for(auto &vecToSort : list) 2 std::sort(std::begin(vecToSort), std::end(vecToSort)); 3 4 /* --- */ 5 6 for_each(std::experimental::parallel::par, 7 std::begin(list), std::end(list), 8 [](std::vector<size_t> &vecToSort){ 9 sort(std::experimental::parallel::par, 10 std::begin(vecToSort), std::end(vecToSort));
  26. 26. Race! 1 auto work=[](decltype(list.end()) begin,decltype(list.end()) end){ 2 for(auto it = begin; it != end; ++it) 3 std::sort(it->begin(), it->end()); 4 }; 5 size_t hc = std::thread::hardware_concurrency(); 6 if(hc == 0) hc = 8; 7 auto numThreads = std::min(list.size(), hc); 8 auto chunkPerThread = list.size() / numThreads; 9 auto threadBegin = list.begin(); 10 auto threadEnd = threadBegin; 11 std::advance(threadEnd, chunkPerThread); 12 std::list<std::future<void>> futures; 13 for(size_t thId = 0; thId < numThreads - 1; ++thId){ 14 futures.push_back(std::async(std::launch::async, 15 work, threadBegin, threadEnd)); 16 threadBegin = threadEnd; 17 std::advance(threadEnd, chunkPerThread); 18 } 19 work(threadBegin, list.end()); 20 for(auto &f : futures) f.get();
  27. 27. И победителем стал... [2 физических, 4 h-threading ядра] Тест #2 Реализация Политика Ускорение Количество строк Parallel STL par/par ∼2.5 2 mine async/seq ∼2.5 20
  28. 28. Динамические политики 1 std::experimental::parallel::execution_policy outerPolicy = 2 std::experimental::parallel::seq; 3 std::experimental::parallel::execution_policy innerPolicy = 4 std::experimental::parallel::par; 5 6 /* Decide at runtime which policy should be */ 7 outerPolicy = std::experimental::parallel::par; 8 innerPolicy = std::experimental::parallel::par_vec; 9 10 for_each(outerPolicy, 11 std::begin(list), std::end(list), 12 [&innerPolicy](std::vector<size_t> &vecToSort){ 13 sort(innerPolicy, 14 std::begin(vecToSort), 15 std::end(vecToSort)); 16 });
  29. 29. Если что-то пошло не так 1 struct Processor 2 { 3 void doWork(size_t id) { 4 throw std::runtime_error("`o´o_ from " + std::to_string(id)); 5 } 6 }; 7 8 std::vector<Processor> processors; 9 processors.resize(25); 10 11 try { 12 std::cout << "Plain for" << std::endl; 13 std::for_each(processors.begin(), processors.end(), 14 [&](Processor &p){ 15 p.doWork(&p - processors.data()); 16 }); 17 } 18 catch(std::exception &e) { 19 std::cout << e.what() << std::endl; 20 }
  30. 30. Ловим исключения 1 try { 2 std::cout << "Seq for" << std::endl; 3 hpx::parallel::for_each(hpx::parallel::seq, 4 processors.begin(), processors.end(), 5 [&](Processor &p){ 6 p.doWork(&p - processors.data()); 7 }); 8 } 9 catch(hpx::parallel::exception_list &list) { 10 std::cout << "Exception list what: " << list.what() << std::endl; 11 std::cout << "Total " << list.size() << " exceptions:" << std:: endl; 12 for(auto &e : list) { 13 try{ boost::rethrow_exception(e); } 14 catch(std::exception &e){ 15 std::cout << "twhat: " << e.what() << std::endl; 16 } 17 } 18 } 19 catch(std::exception &e){ 20 std::cout << e.what() << std::endl; 21 }
  31. 31. Ловим исключения Plain for `o_´o from 0 Seq for Exception list what: HPX(unknown_error) Total 1 exceptions: what: HPX(unknown_error) Par for Exception list what: HPX(unknown_error) Total 13 exceptions: what: `o_´o from 0 what: `o_´o from 2 what: `o_´o from 4 what: `o_´o from 6 what: `o_´o from 8 what: `o_´o from 10 what: `o_´o from 12 what: `o_´o from 14 what: `o_´o from 16 what: `o_´o from 18 what: `o_´o from 20 what: `o_´o from 22 what: `o_´o from 24
  32. 32. На десерт Parallelism Extension for STL еще не реализовано в компиляторах. 1 #include <algorithm> 2 3 std::some_standard_algorythm_with_stl_containers( 4 std::begin(container), 5 std::end(container) 6 ); Но если используется gcc, то...
  33. 33. На десерт Parallelism Extension for STL еще не реализовано в компиляторах. 1 #include <algorithm> 2 3 std::some_standard_algorythm_with_stl_containers( 4 std::begin(container), 5 std::end(container) 6 ); Но если используется gcc, то... 1 //OpenMP should be enabled 2 3 #include <parallel/algorithm> 4 5 std::__parallel::some_standard_algorythm_with_stl_containers( 6 std::begin(container), 7 std::end(container) 8 );
  34. 34. Те же самые примеры 1 std::__parallel::sort(std::begin(vecToSort), std::end(vecToSort)); 2 3 std::__parallel::for_each(std::begin(list), std::end(list), 4 [](std::vector<size_t> &vecToSort){ 5 std::__parallel::sort(std::begin(vecToSort), std::end( vecToSort)); 6 }); 7 8 #pragma omp parallel 9 { 10 #pragma omp single 11 { 12 for(auto it = list.begin(); it != list.end(); ++it) 13 #pragma omp task 14 sort(it->begin(), it->end()); 15 } 16 }
  35. 35. Результаты [2 физических, 4 h-threading ядра] Тест #2 Реализация Политика Ускорение Количество строк Parallel STL par/par ∼2.5 2 mine (std::async) async/seq ∼2.5 20 Parallel gcc par/par ∼2.5 2 mine (openmp) task/seq ∼2.5 9

×