std::exclusive_scan和std::inclusive_scan默认为串行算法,需显式传入并行策略(如std::execution::par_unseq)才可并行;核心区别在于inclusive含当前元素累加,exclusive不含,首项恒为init。

std::exclusive_scan 和 std::inclusive_scan 不是“并行扫描算法”——它们本身是顺序执行的,但支持传入并行执行策略(如 std::execution::par_unseq),才能真正并行化。不显式指定策略时,就是普通串行扫描,性能甚至略低于手写循环。
exclusive\_scan 与 inclusive\_scan 的行为差异
核心区别只在“是否包含当前元素”:
-
inclusive_scan:输出第
i项 =init + op(init, first[0]) + ... + op(..., first[i]),即累加到当前元素为止(含) -
exclusive_scan:输出第
i项 =init + op(init, first[0]) + ... + op(..., first[i-1]),即累加到前一个元素(不含当前),第 0 项直接为init
例如对 {1,2,3,4} 执行加法扫描、init=0:
inclusive: {0+1, 0+1+2, 0+1+2+3, 0+1+2+3+4} → {1,3,6,10}
exclusive: {0, 0+1, 0+1+2, 0+1+2+3} → {0,1,3,6}
注意:exclusive_scan 输出首项恒为 init,且输出迭代器长度必须 ≥ 输入长度(否则越界)。
必须传 execution policy 才能并行
这两个函数默认是顺序语义,即使你用 std::vector 配合多核 CPU,也不会自动并行。要启用并行,必须显式传入执行策略:
-
std::execution::seq:强制顺序(调试用) -
std::execution::par:允许并行,但保留顺序副作用(如自定义二元操作含std::cout会乱序) -
std::execution::par_unseq:允许并行 + 向量化(推荐用于纯计算,如加法、乘法)
错误写法:std::exclusive_scan(first, last, out, init); → 仍是串行
正确并行写法:std::exclusive_scan(std::execution::par_unseq, first, last, out, init);
编译需加 -std=c++17 或更高,且链接 libtbb 或 libstdc++ 并行实现(GCC 11+ 自带,Clang 需额外配置)。
常见崩溃与误用点
最容易出错的是迭代器范围和输出空间分配:
-
exclusive_scan要求out至少有last - first + 1个可写位置(因为首项填init,后续每项对应一个输入) - 若输出区间与输入区间重叠(如原地扫描),
inclusive_scan允许输入与输出起始地址相同;但exclusive_scan**不允许** —— 因为它先写init到*out,而该位置可能正是*first,导致后续读取被覆盖 - 自定义二元操作(
BinaryOp)必须满足结合律,否则并行结果不确定。加法、乘法可以;减法、除法、字符串拼接不行 - 使用
std::execution::par_unseq时,操作不能有数据竞争或外部依赖(比如修改全局计数器)
典型报错:std::bad_alloc(输出空间不足)、segmentation fault(原地 exclusive_scan 覆盖输入)、结果随机波动(操作不满足结合律)。
真正决定性能的不是函数名,而是你是否配对了正确的执行策略、是否确保操作无副作用、以及是否给足输出空间——这些细节漏掉一个,并行就退化成 bug。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











