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后续版本能尽快完成该特性的标准化。
1
下面是采用C++ 17标准执行策略方式实现并行计算的一个简单示例。该示例虽然符合C++ 17标准,但在我写作本文时,使用最新版本的GCC 7.3不能编译通过。

#include <algorithm>
#include <execution>
#include <iostream>
#include <random>
#include <vector>

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并行算法:

AlgorithmHeaderParallel algorithmParallel header
std::accumulatenumeric__gnu_parallel::accumulateparallel/numeric
std::adjacent_differencenumeric__gnu_parallel::adjacent_differenceparallel/numeric
std::inner_productnumeric__gnu_parallel::inner_productparallel/numeric
std::partial_sumnumeric__gnu_parallel::partial_sumparallel/numeric
std::adjacent_findalgorithm__gnu_parallel::adjacent_findparallel/algorithm
std::countalgorithm__gnu_parallel::countparallel/algorithm
std::count_ifalgorithm__gnu_parallel::count_ifparallel/algorithm
std::equalalgorithm__gnu_parallel::equalparallel/algorithm
std::findalgorithm__gnu_parallel::findparallel/algorithm
std::find_ifalgorithm__gnu_parallel::find_ifparallel/algorithm
std::find_first_ofalgorithm__gnu_parallel::find_first_ofparallel/algorithm
std::for_eachalgorithm__gnu_parallel::for_eachparallel/algorithm
std::generatealgorithm__gnu_parallel::generateparallel/algorithm
std::generate_nalgorithm__gnu_parallel::generate_nparallel/algorithm
std::lexicographical_comparealgorithm__gnu_parallel::lexicographical_compareparallel/algorithm
std::mismatchalgorithm__gnu_parallel::mismatchparallel/algorithm
std::searchalgorithm__gnu_parallel::searchparallel/algorithm
std::search_nalgorithm__gnu_parallel::search_nparallel/algorithm
std::transformalgorithm__gnu_parallel::transformparallel/algorithm
std::replacealgorithm__gnu_parallel::replaceparallel/algorithm
std::replace_ifalgorithm__gnu_parallel::replace_ifparallel/algorithm
std::max_elementalgorithm__gnu_parallel::max_elementparallel/algorithm
std::mergealgorithm__gnu_parallel::mergeparallel/algorithm
std::min_elementalgorithm__gnu_parallel::min_elementparallel/algorithm
std::nth_elementalgorithm__gnu_parallel::nth_elementparallel/algorithm
std::partial_sortalgorithm__gnu_parallel::partial_sortparallel/algorithm
std::partitionalgorithm__gnu_parallel::partitionparallel/algorithm
std::random_shufflealgorithm__gnu_parallel::random_shuffleparallel/algorithm
std::set_unionalgorithm__gnu_parallel::set_unionparallel/algorithm
std::set_intersectionalgorithm__gnu_parallel::set_intersectionparallel/algorithm
std::set_symmetric_differencealgorithm__gnu_parallel::set_symmetric_differenceparallel/algorithm
std::set_differencealgorithm__gnu_parallel::set_differenceparallel/algorithm
std::sortalgorithm__gnu_parallel::sortparallel/algorithm
std::stable_sortalgorithm__gnu_parallel::stable_sortparallel/algorithm
std::unique_copyalgorithm__gnu_parallel::unique_copyparallel/algorithm

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

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

#include <iostream>
#include <parallel/algorithm>
#include <random>
#include <vector>

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并行算法。
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值