std::set_difference
std::set_intersection
std::set_symmetric_difference
std::set_union
std::sort
std::stable_partition
std::stable_sort
std::swap_ranges
std::transform
std::transform_exclusive_scan
std::transform_inclusive_scan
std::transform_reduce
std::uninitialized_copy
std::uninitialized_copy_n
std::uninitialized_fill
std::uninitialized_fill_n
std::unique
std::unique_copy
Улучшение этих алгоритмов — отличная новость! Чем больше алгоритмов STL используется в наших старых программах, тем проще добавить поддержку параллелизма задним числом. Обратите внимание: это не значит, что такие изменения автоматически сделают программу в
Однако вместо того, чтобы разрабатывать собственные сложные параллельные алгоритмы с помощью std::thread
std::async
или внешних библиотек, можно распараллелить выполнение стандартных задач способом, не зависящим от операционной системы.Как работают эти политики выполнения
Политика выполнения указывает, какую стратегию автоматического распараллеливания необходимо использовать при вызове стандартных алгоритмов.
Следующие три типа политик существуют в пространстве имен std::execution
Политики выполнения подразумевают конкретные ограничения. Чем они строже, тем больше мер по распараллеливанию можно позволить:
□ все элементы функций доступа, используемые параллелизованными алгоритмами,
□ в случае параллелизации и векторизации все функции получения доступа
До тех пор, пока подчиняемся этим правилам, мы не столкнемся с ошибками, которые могут появиться в параллельных версиях алгоритмов STL.
Что означает понятие «векторизация»
Векторизация — это свойство, которое должны поддерживать как процессор, так и компилятор. Кратко рассмотрим простой пример, чтобы понять суть векторизации и как она работает. Допустим, нужно сложить числа, находящиеся в очень большом векторе. Простая реализация данной задачи может выглядеть так:
std::vector
int sum {std::accumulate(v.begin(), v.end(), 0)};
Компилятор в конечном счете сгенерирует цикл из вызова accumulate
int sum {0};
for (size_t i {0}; i < v.size(); ++i) {
sum += v[i];
}
С этого момента при разрешенной и включенной векторизации компилятор может создать следующий код. Цикл выполняет четыре шага сложения в одной итерации цикла, что сокращает количество итераций в четыре раза. Для простоты пример не работает с остатком, если вектор не содержит N*4
int sum {0};
for (size_t i {0}; i < v.size() / 4; i += 4) {
sum += v[i] + v[i+1] + v[i + 2] + v[i + 3];
}
// если операция v.size()/4 имеет остаток,
// в реальном коде также нужно это обработать.
Зачем это делать? Многие процессоры предоставляют инструкции, которые могут выполнять математические операции наподобие sum += v[i]+v[i+1]+v[i+2]+v[i+3];
Автоматическую векторизацию выполнять сложно, поскольку компилятору нужно в некоторой степени понимать нашу программу, чтобы ускорить ее, не нарушая
Приостанавливаем программу на конкретный промежуток времени