C++17标准STL库并行策略在GCC编译器中的替代实现方法

C++17标准STL库并行策略在GCC编译器中的替代实现方法

严正声明:本文系作者davidhopper原创,未经许可,不得转载。

2019年8月5日更新:
GCC 9.1.0可支持C++ 17标准的并行策略,本文方法已过时。
参考我的另一篇博客:《Ubuntu 16.04系统中使用GCC 9.1及Intel TBB库运行C++17 STL并行算法库》。

一、引言

C++ 17标准中一个令人兴奋的特性是对STL库中的69个算法加入了执行策略(execution policies),允许在少量修改的情形下,对原有STL库算法实现并行计算,这对希望提高效率的开发者无疑是一个很大的福音。令人遗憾地是,目前主流C++编译器尚未加入对该特性的支持。
GCC编译器作为Linux系统中最主流的编译器,其最新版本GCC 7.3几乎完全实现了对C++ 17标准的支持(如下图所示),也可完成多数STL库算法的并行计算,但它不是以C++ 17标准所规范的基于执行策略来实现,而是以添加额外头文件和编译选项的方式来完成,希望GCC后续版本能尽快完成该特性的标准化。
C++17标准STL库并行策略在GCC编译器中的替代实现方法_第1张图片
下面是采用C++ 17标准执行策略方式实现并行计算的一个简单示例。该示例虽然符合C++ 17标准,但在我写作本文时,使用最新版本的GCC 7.3不能编译通过。

#include 
#include 
#include 
#include 
#include 

using namespace std;

bool odd(int n) { return n % 2; }

int main() {
  vector<int> d(500);

  mt19937 gen;
  uniform_int_distribution<int> dis(0, 100000);

  auto rand_num([=]() mutable { return dis(gen); });

  generate(execution::par, begin(d), end(d), rand_num);

  sort(execution::par, begin(d), end(d));

  reverse(execution::par, begin(d), end(d));

  auto odds(count_if(execution::par, begin(d), end(d), odd));

  cout << (100.0 * odds / d.size()) << "% of the numbers are odd.\n";

  return 0;
}

在GCC编译器尚未支持C++ 17标准执行策略的情形下,我们以什么方式完成C++ STL库算法的并行计算呢?本文将以Ubuntu 16.04自带的GCC 5.4编译器对其进行阐述。

二、GCC编译器使用并行计算所需的编译选项

使用GCC编译器编译并行计算程序需要OpenMP库的支持,目前GCC编译器自带该库,无需自己下载,只需在编译程序时添加一个额外选项-fopenmp即可,该选项会指示编译器链接并行计算库:libgomp

GCC编译器在某些硬件平台(例如sparc和x86)上默认不支持原子操作(atomic operations),要在这些硬件平台上编译并行计算程序,需显式指定额外的编译选项,如:-march=i686-march=native-mcpu=v9,更多信息请查阅GCC编译器使用手册。

为使用libstdc++并行模式,还需在编译程序时添加一个宏定义: -D_GLIBCXX_PARALLEL. 该宏会指示编译器在可行的前提下,使用STL库算法的并行计算版本。

注意:并非所有的STL库算法在GCC编译器中都有对应的并行计算版本,下表给出了GCC编译器所支持的STL并行算法:

Algorithm Header Parallel algorithm Parallel header
std::accumulate numeric __gnu_parallel::accumulate parallel/numeric
std::adjacent_difference numeric __gnu_parallel::adjacent_difference parallel/numeric
std::inner_product numeric __gnu_parallel::inner_product parallel/numeric
std::partial_sum numeric __gnu_parallel::partial_sum parallel/numeric
std::adjacent_find algorithm __gnu_parallel::adjacent_find parallel/algorithm
std::count algorithm __gnu_parallel::count parallel/algorithm
std::count_if algorithm __gnu_parallel::count_if parallel/algorithm
std::equal algorithm __gnu_parallel::equal parallel/algorithm
std::find algorithm __gnu_parallel::find parallel/algorithm
std::find_if algorithm __gnu_parallel::find_if parallel/algorithm
std::find_first_of algorithm __gnu_parallel::find_first_of parallel/algorithm
std::for_each algorithm __gnu_parallel::for_each parallel/algorithm
std::generate algorithm __gnu_parallel::generate parallel/algorithm
std::generate_n algorithm __gnu_parallel::generate_n parallel/algorithm
std::lexicographical_compare algorithm __gnu_parallel::lexicographical_compare parallel/algorithm
std::mismatch algorithm __gnu_parallel::mismatch parallel/algorithm
std::search algorithm __gnu_parallel::search parallel/algorithm
std::search_n algorithm __gnu_parallel::search_n parallel/algorithm
std::transform algorithm __gnu_parallel::transform parallel/algorithm
std::replace algorithm __gnu_parallel::replace parallel/algorithm
std::replace_if algorithm __gnu_parallel::replace_if parallel/algorithm
std::max_element algorithm __gnu_parallel::max_element parallel/algorithm
std::merge algorithm __gnu_parallel::merge parallel/algorithm
std::min_element algorithm __gnu_parallel::min_element parallel/algorithm
std::nth_element algorithm __gnu_parallel::nth_element parallel/algorithm
std::partial_sort algorithm __gnu_parallel::partial_sort parallel/algorithm
std::partition algorithm __gnu_parallel::partition parallel/algorithm
std::random_shuffle algorithm __gnu_parallel::random_shuffle parallel/algorithm
std::set_union algorithm __gnu_parallel::set_union parallel/algorithm
std::set_intersection algorithm __gnu_parallel::set_intersection parallel/algorithm
std::set_symmetric_difference algorithm __gnu_parallel::set_symmetric_difference parallel/algorithm
std::set_difference algorithm __gnu_parallel::set_difference parallel/algorithm
std::sort algorithm __gnu_parallel::sort parallel/algorithm
std::stable_sort algorithm __gnu_parallel::stable_sort parallel/algorithm
std::unique_copy algorithm __gnu_parallel::unique_copy parallel/algorithm

三、GCC编译器使用STL并行算法的简单示例

对引言中不能顺利通过编译的示例进行改造,以便能借助GCC编译器实现STL并行算法,下面列出改进后的代码:

#include 
#include 
#include 
#include 

using namespace std;
using namespace __gnu_parallel;

bool odd(int n) { return n % 2; }

int main() {
  // Set up the parallel mode. 
  _Settings s;
  s.algorithm_strategy = force_parallel;
  _Settings::set(s);

  vector<int> d(500);

  mt19937 gen;
  uniform_int_distribution<int> dis(0, 100000);

  auto rand_num([=]() mutable { return dis(gen); });

  generate(begin(d), end(d), rand_num);

  sort(begin(d), end(d));

  reverse(begin(d), end(d));

  auto odds(count_if(begin(d), end(d), odd));

  cout << (100.0 * odds / d.size()) << "% of the numbers are odd.\n";

  return 0;
}

编译指令为(注意-std=c++17替换为-std=c++11-std=c++14均可):

g++ -g -Wall -D_GLIBCXX_PARALLEL -fopenmp -std=c++17 gcc_parallel.cpp -o gcc_parallel

在我机器上的运行结果为:

53% of the numbers are odd.

四、说明

  1. GCC编译器中的STL并行算法是非异常安全的,也就是说,用户自定义函数(例如上例中的odd函数)、Lambda表达式(例如上例中的[=]() mutable { return dis(gen); })或函数对象内部绝不能抛出异常;
  2. 算法执行的顺序不确定,因为用户自定义函数、Lambda表达式或函数对象内部不能依赖于算法的执行顺序;
  3. 因为GCC OPenMP不支持在多线程中运行,因此不要尝试在多线程中调用STL并行算法。

你可能感兴趣的:(C++)