std::transform搭配std::execution::par_unseq未生效,如何利用全部CPU核心?
用户编写了以下C++20代码,意图通过std::transform结合std::execution::par_unseq执行并行转换以利用16核CPU,但运行后发现并行版本与串行版本的transform操作耗时差异较小,硬件并发数未被充分利用。
代码实现
#include <iostream> #include <numeric> #include <vector> #include <cmath> #include <execution> #include <chrono> using std::cout; using std::endl; static auto start_time = std::chrono::high_resolution_clock::now(); // 启动计时器 void tick() { start_time = std::chrono::high_resolution_clock::now(); } // 停止计时器并输出耗时(毫秒) void tock() { auto end_time = std::chrono::high_resolution_clock::now(); auto duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count(); std::cout << "Elapsed time: " << duration_ms << " ms" << std::endl; } double compute_p_n(double m, int n) { return 0.5 * (1 - std::pow(1 - 2 * m, n)); } void do_work(auto pol) { const double m = 1e-2; const int k = 3e8; std::vector<double> input(k); std::vector<double> results(k); tick(); std::iota(input.begin(), input.end(), 1); // 初始化为1、2、3……k cout << "initializing with indices: "; tock(); tick(); std::transform(pol, input.begin(), input.end(), results.begin(), [m](double n) { return compute_p_n(m, n); }); // 计算p(n) cout << "transform: "; tock(); } int main() { cout << "hw concur: " << std::thread::hardware_concurrency() << endl; cout << "parallel: " << endl; do_work(std::execution::par_unseq); cout << endl << "sequential: " << endl; do_work(std::execution::seq); return 0; }
编译与运行信息
编译命令:g++ compute_prob.cpp --std=c++20 && ./a.out
运行结果:
硬件并发数:16
并行版本:
初始化索引耗时:1389 毫秒
transform操作耗时:5303 毫秒串行版本:
初始化索引耗时:1366 毫秒
transform操作耗时:6576 毫秒
解决方案
启用并行库链接:GCC的并行STL依赖Intel TBB库,默认编译不会自动链接。修改编译命令为:
g++ compute_prob.cpp --std=c++20 -ltbb && ./a.out
若系统未安装TBB,需先安装(例如Ubuntu执行sudo apt install libtbb-dev)。优化任务计算粒度:当前每个
compute_p_n的计算量极小,线程调度开销远超并行收益。可以将多个元素的计算合并为单个任务,或增大单次计算的复杂度,让线程有足够的计算量分摊调度成本。缓解内存带宽瓶颈:
k=3e8的两个double类型vector共占用约4.8GB内存,内存带宽可能成为瓶颈限制CPU利用率。可尝试减小k的值,或改用float类型(若精度允许)降低内存占用。验证并行执行状态:运行时用
htop或top工具观察CPU使用率,确认并行版本是否真正启用多核心计算。若编译器未正确启用并行支持,par_unseq会自动退化为串行执行。优化计算函数性能:
std::pow调用存在性能瓶颈,可替换为快速幂实现或近似计算方法,提升单线程计算效率,进而放大并行加速效果。
内容的提问来源于stack exchange,提问作者Bob

