Parallel STL
-
Upload
evgeny-krutko -
Category
Software
-
view
975 -
download
1
Transcript of Parallel STL
C++17: параллельная версия стандартныхалгоритмов
Евгений Крутько[email protected]
Национальный исследовательский центр "Курчатовский институт"
25.02.2017
Эволюция многопоточной работы в C++Нам нужно выполнить работу асинхронно
1 int main(){2 //Here we should use some features to call doWork in parallel3 doWork(int a);4 doSomeOtherWork(double t);5 //Ensure doWork completed6 return 0;7 }
Давным-давно...... у нас не было даже стандарта C++11.
1 //Using POSIX API2 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 }
Давным-давно...... у нас не было даже стандарта C++11.
1 //Using Win API2 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 NULL16 );17 doSomeOtherWork(double t);18 WaitForSingleObject(thread, INFINITE);19 return 0;20 }
Кросс-платформенный подход
И другие
Начиная со стандарта C++11std::threadstd::async / std::future
1 int main(int argc, char**argv) {2 std::thread thread;3 int value;4 //Do doWork() in new thread5 thread = std::thread(6 doWork,7 std::ref(value));8 //Do someything else in this thread9 doSomeOtherWork();
10 //Whait for doWork() finishes11 thread.join();12 return 0;13 }
Начиная со стандарта C++11std::threadstd::async / std::future
1 int main(int argc, char**argv) {2 int value;3 //Do doWork() in new thread4 auto future = std::async(5 std::launch::async;6 doWork,7 std::ref(value));8 //Do someything else in this thread9 doSomeOtherWork();
10 //Whait for doWork() finishes11 future.get();12 return 0;13 }
Нам и этого мало, хочется еще прощеВозможно когда-нибудь :)
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 policy7 std::some_standard_algorythm_with_stl_containers(8 ExecutionPolicy policy,9 std::begin(container),
10 std::end(container)11 );
Нам и этого мало, хочется еще прощеВозможно когда-нибудь :)
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 policy7 std::some_standard_algorythm_with_stl_containers(8 ExecutionPolicy policy,9 std::begin(container),
10 std::end(container)11 );
Алгоритмы стандартной библиотеки, доступные впараллельном режиме
Объявлены в заголовочном файле <algorithm>Пока в стадии TS в заголовочном файле<experimental/algorithm>
adjacent_difference adjacent_find all_of any_ofcopy copy_if copy_n countcount_if equal exclusive_scan fillfill_n find find_end find_first_offind_if find_if_not for_each for_each_ngenerate generate_n includes inclusive_scaninner_product inplace_merge is_heap is_heap_untilis_partitioned is_sorted is_sorted_until lexicographical_comparemax_element merge min_element minmax_elementmismatch move none_of nth_elementpartial_sort partial_sort_copy partition partition_copyreduce remove remove_copy remove_copy_ifremove_if replace replace_copy replace_copy_ifreplace_if reverse reverse_copy rotaterotate_copy search search_n set_differenceset_intersection set_symmetric_difference set_union sortstable_partition stable_sort swap_ranges transformtransform_exclusive_scan transform_inclusive_scan transform_reduce uninitialized_copyuninitialized_copy_n uninitialized_fill uninitialized_fill_n uniqueunique_copy
Политики выполнения1 //Available from <execution_policy>2 //While in TS stage from <experimental/execution_policy>3
4 //Plain old sequenced execution5 constexpr sequential_execution_policy seq{ };6 //Parallel execution7 constexpr parallel_execution_policy par{ };8 //Parallel with SIMD instructions9 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 race8 });
Политики выполнения1 //Available from <execution_policy>2 //While in TS stage from <experimental/execution_policy>3
4 //Plain old sequenced execution5 constexpr sequential_execution_policy seq{ };6 //Parallel execution7 constexpr parallel_execution_policy par{ };8 //Parallel with SIMD instructions9 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 race8 });
Дайте две!Как же попробовать? Из документаhttps://isocpp.org/files/papers/P0024R2.html
Microsoft MS ParallelSTL pageHPX HPX githubCodeplay Sycl githubHSA HSA for math science pageThibaut Lutz githubNVIDIA github
http://github.com/eskrut/ParallelSTL.git
Тестовая задача #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");
Тестовая задача #1С последовательной политикой
1 //This includes should be from ${T_LUTZ_ROOT}/include2 #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");
Тестовая задача #1С параллельной политикой
1 //This includes should be from ${T_LUTZ_ROOT}/include2 #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");
Тестовая задача #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");
Тестовая задача #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");
Тестовая задача #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");
Тестовая задача #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");
Тестовая задача #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));
Тестовая задача #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 });
Тестовая задача #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 });
Результаты
[2 физических, 4 h-threading ядра]Тест #1
Реализация Политика Ускорение
t-lutzseq 1par 2.37
HPXseq 0.99par 2.61
par_vec 2.64
Тест #2Реализация Политика for Политика sort Ускорение
t-lutzseq par 2.26par seq 2.78par par 2.48
HPXseq par 2.52par seq 2.75par par 2.87
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));
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();
И победителем стал...
[2 физических, 4 h-threading ядра]
Тест #2Реализация Политика Ускорение Количество строкParallel STL par/par ∼2.5 2
mine async/seq ∼2.5 20
Динамические политики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 });
Если что-то пошло не так1 struct Processor2 {3 void doWork(size_t id) {4 throw std::runtime_error("oo_ 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 }
Ловим исключения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 }
Ловим исключенияPlain foro_o from 0Seq forException list what: HPX(unknown_error)Total 1 exceptions:
what: HPX(unknown_error)Par forException list what: HPX(unknown_error)Total 13 exceptions:
what: o_o from 0what: o_o from 2what: o_o from 4what: o_o from 6what: o_o from 8what: o_o from 10what: o_o from 12what: o_o from 14what: o_o from 16what: o_o from 18what: o_o from 20what: o_o from 22what: o_o from 24
На десерт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 enabled2
3 #include <parallel/algorithm>4
5 std::__parallel::some_standard_algorythm_with_stl_containers(6 std::begin(container),7 std::end(container)8 );
На десерт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 enabled2
3 #include <parallel/algorithm>4
5 std::__parallel::some_standard_algorythm_with_stl_containers(6 std::begin(container),7 std::end(container)8 );
Те же самые примеры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 parallel9 {
10 #pragma omp single11 {12 for(auto it = list.begin(); it != list.end(); ++it)13 #pragma omp task14 sort(it->begin(), it->end());15 }16 }
Результаты
[2 физических, 4 h-threading ядра]
Тест #2Реализация Политика Ускорение Количество строкParallel STL par/par ∼2.5 2
mine (std::async) async/seq ∼2.5 20Parallel gcc par/par ∼2.5 2
mine (openmp) task/seq ∼2.5 9