C++17 多线程系列(五):C++17 并行算法——从串行到并行的零成本迁移

核心目标:掌握 C++17 引入的并行算法执行策略,能将现有 STL 算法用一行改动改造为并行版本,理解何时并行化真正有效,避免并行化陷阱。

前置知识:Part 1 的线程基础,Part 2 的同步概念,熟悉 STL 算法(sortfor_eachtransform 等)。


5.1 执行策略总览

5.1.1 三种策略

C++17 在 <execution> 头文件中定义了三种执行策略:

cpp 复制代码
#include <execution>
// 不需要链接额外库,头文件即可用
策略 含义 并行方式 适用场景
std::execution::seq 串行执行 单线程 与无策略等价,显式表明意图
std::execution::par 并行执行 多线程 CPU 密集型、数据量大
std::execution::par_unseq 并行 + 向量化 多线程 + SIMD 计算简单密集、可向量化

#mermaid-svg-dxMUH6OJ9NWrmYpd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dxMUH6OJ9NWrmYpd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dxMUH6OJ9NWrmYpd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dxMUH6OJ9NWrmYpd .error-icon{fill:#552222;}#mermaid-svg-dxMUH6OJ9NWrmYpd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dxMUH6OJ9NWrmYpd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dxMUH6OJ9NWrmYpd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dxMUH6OJ9NWrmYpd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dxMUH6OJ9NWrmYpd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dxMUH6OJ9NWrmYpd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dxMUH6OJ9NWrmYpd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dxMUH6OJ9NWrmYpd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dxMUH6OJ9NWrmYpd .marker.cross{stroke:#333333;}#mermaid-svg-dxMUH6OJ9NWrmYpd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dxMUH6OJ9NWrmYpd p{margin:0;}#mermaid-svg-dxMUH6OJ9NWrmYpd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dxMUH6OJ9NWrmYpd .cluster-label text{fill:#333;}#mermaid-svg-dxMUH6OJ9NWrmYpd .cluster-label span{color:#333;}#mermaid-svg-dxMUH6OJ9NWrmYpd .cluster-label span p{background-color:transparent;}#mermaid-svg-dxMUH6OJ9NWrmYpd .label text,#mermaid-svg-dxMUH6OJ9NWrmYpd span{fill:#333;color:#333;}#mermaid-svg-dxMUH6OJ9NWrmYpd .node rect,#mermaid-svg-dxMUH6OJ9NWrmYpd .node circle,#mermaid-svg-dxMUH6OJ9NWrmYpd .node ellipse,#mermaid-svg-dxMUH6OJ9NWrmYpd .node polygon,#mermaid-svg-dxMUH6OJ9NWrmYpd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dxMUH6OJ9NWrmYpd .rough-node .label text,#mermaid-svg-dxMUH6OJ9NWrmYpd .node .label text,#mermaid-svg-dxMUH6OJ9NWrmYpd .image-shape .label,#mermaid-svg-dxMUH6OJ9NWrmYpd .icon-shape .label{text-anchor:middle;}#mermaid-svg-dxMUH6OJ9NWrmYpd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dxMUH6OJ9NWrmYpd .rough-node .label,#mermaid-svg-dxMUH6OJ9NWrmYpd .node .label,#mermaid-svg-dxMUH6OJ9NWrmYpd .image-shape .label,#mermaid-svg-dxMUH6OJ9NWrmYpd .icon-shape .label{text-align:center;}#mermaid-svg-dxMUH6OJ9NWrmYpd .node.clickable{cursor:pointer;}#mermaid-svg-dxMUH6OJ9NWrmYpd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dxMUH6OJ9NWrmYpd .arrowheadPath{fill:#333333;}#mermaid-svg-dxMUH6OJ9NWrmYpd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dxMUH6OJ9NWrmYpd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dxMUH6OJ9NWrmYpd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dxMUH6OJ9NWrmYpd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dxMUH6OJ9NWrmYpd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dxMUH6OJ9NWrmYpd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dxMUH6OJ9NWrmYpd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dxMUH6OJ9NWrmYpd .cluster text{fill:#333;}#mermaid-svg-dxMUH6OJ9NWrmYpd .cluster span{color:#333;}#mermaid-svg-dxMUH6OJ9NWrmYpd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dxMUH6OJ9NWrmYpd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dxMUH6OJ9NWrmYpd rect.text{fill:none;stroke-width:0;}#mermaid-svg-dxMUH6OJ9NWrmYpd .icon-shape,#mermaid-svg-dxMUH6OJ9NWrmYpd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dxMUH6OJ9NWrmYpd .icon-shape p,#mermaid-svg-dxMUH6OJ9NWrmYpd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dxMUH6OJ9NWrmYpd .icon-shape .label rect,#mermaid-svg-dxMUH6OJ9NWrmYpd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dxMUH6OJ9NWrmYpd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dxMUH6OJ9NWrmYpd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dxMUH6OJ9NWrmYpd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} seq
par
par_unseq
输入数据 (N 个元素)
执行策略
单线程

逐元素处理
多线程

分片并行处理

Thread 1 ████

Thread 2 ████

Thread 3 ████

Thread 4 ████
多线程 + SIMD

每个线程内部向量化

一次处理 4/8/16 个元素

5.1.2 第一个并行算法

cpp 复制代码
#include <algorithm>
#include <execution>
#include <vector>
#include <chrono>
#include <iostream>

int main() {
    std::vector<int> v(10'000'000);
    std::generate(v.begin(), v.end(), std::rand);

    // ── 串行版本 ──
    auto v1 = v;
    auto t1 = std::chrono::steady_clock::now();
    std::sort(v1.begin(), v1.end());                    // 串行
    auto t2 = std::chrono::steady_clock::now();

    // ── 并行版本 ------ 只需改一行! ──
    auto v2 = v;
    auto t3 = std::chrono::steady_clock::now();
    std::sort(std::execution::par, v2.begin(), v2.end()); // 并行!
    auto t4 = std::chrono::steady_clock::now();

    auto seq_ms = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
    auto par_ms = std::chrono::duration_cast<std::chrono::milliseconds>(t4 - t3).count();

    std::cout << "串行: " << seq_ms << "ms\n";
    std::cout << "并行: " << par_ms << "ms\n";
    std::cout << "加速: " << (double)seq_ms / par_ms << "x\n";
    // 典型输出 (8核):
    // 串行: 2140ms
    // 并行: 410ms
    // 加速: 5.2x
}

编译要求

bash 复制代码
g++ -std=c++17 -pthread -O2 -o parallel_sort parallel_sort.cpp
# GCC 9+ 需要安装 Intel TBB:
sudo apt install libtbb-dev
g++ -std=c++17 -pthread -O2 -o parallel_sort parallel_sort.cpp -ltbb

5.2 最简单的并行化------一行改动

5.2.1 std::sort ------ 只需加一个参数

cpp 复制代码
// 串行
std::sort(vec.begin(), vec.end());

// 并行 (C++17)
std::sort(std::execution::par, vec.begin(), vec.end());

// 并行 + 向量化
std::sort(std::execution::par_unseq, vec.begin(), vec.end());

5.2.2 std::for_each ------ 并行遍历

cpp 复制代码
std::vector<double> data(1'000'000);

// 串行
std::for_each(data.begin(), data.end(), [](double& x) {
    x = std::sqrt(x);  // 计算密集型操作
});

// 并行 ------ 每个元素独立,天然适合并行
std::for_each(std::execution::par, data.begin(), data.end(),
              [](double& x) {
    x = std::sqrt(x);
});

5.2.3 std::transform ------ 并行转换

cpp 复制代码
std::vector<int> input(1'000'000);
std::vector<int> output(input.size());

std::transform(std::execution::par,
               input.begin(), input.end(),
               output.begin(),
               [](int x) { return x * x + 2 * x + 1; });

5.2.4 std::copy_if ------ 并行过滤

cpp 复制代码
std::vector<int> big_data(10'000'000);
std::vector<int> filtered;

std::copy_if(std::execution::par,
             big_data.begin(), big_data.end(),
             std::back_inserter(filtered),
             [](int x) { return x % 7 == 0 && x > 1000; });

5.3 支持的算法列表

类别 算法 适合并行? 典型加速比
排序 sort 3-6x
stable_sort 2-4x
partial_sort 2-3x
变换 transform 接近核心数
copy 接近核心数
fill 接近核心数
generate 接近核心数
查找 find / find_if 接近核心数
search 接近核心数
any_of / all_of / none_of 接近核心数
归约 reduce 首选 接近核心数
transform_reduce 接近核心数
数值 exclusive_scan / inclusive_scan 2-4x
归约(旧) accumulate ❌ 不支持并行 ---
合并 merge 2-3x
移除 remove / remove_if 2-4x
分区 partition 2-3x

关键发现std::accumulate 不支持并行(标准未定义并行版本)。请用 std::reduce 替代。


5.4 std::reduce vs std::accumulate

这是 C++17 并行算法中最重要的替代关系:

cpp 复制代码
#include <numeric>
#include <execution>
#include <vector>

std::vector<int> v(10'000'000, 1);

// ❌ std::accumulate: 串行------标准未定义并行版本
int sum1 = std::accumulate(v.begin(), v.end(), 0);

// ✅ std::reduce: 并行归约
int sum2 = std::reduce(std::execution::par, v.begin(), v.end(), 0);
特性 std::accumulate std::reduce
并行支持
结合律要求 无严格要求 必须满足交换律 + 结合律
初始值 必须提供 可不提供(默认 T{})
执行顺序 严格从左到右 未指定
C++ 版本 C++98 C++17

5.4.1 结合律的陷阱

cpp 复制代码
// ✅ 整数加法: 满足交换律和结合律 → reduce 安全
int sum = std::reduce(std::execution::par, v.begin(), v.end(), 0);

// ❌ 字符串拼接: 不满足交换律 → reduce 结果不确定!
std::vector<std::string> words = {"Hello", " ", "World"};
auto result = std::reduce(std::execution::par, words.begin(), words.end(),
                          std::string{},
                          std::plus<>{});  // "World Hello" 或 "Hello World"
// 应该用 std::accumulate 保证顺序

5.4.2 std::transform_reduce ------ 一步到位

cpp 复制代码
// 计算向量的点积 (dot product) 的并行版本
std::vector<double> a(1'000'000), b(1'000'000);

double dot = std::transform_reduce(
    std::execution::par,
    a.begin(), a.end(),
    b.begin(),
    0.0);                       // 初始值
// 等价于 sum(a[i] * b[i])

5.5 并行算法的异常处理

cpp 复制代码
// ⚠️ 并行算法中抛异常 → std::terminate!
std::vector<int> v(1000);
try {
    std::for_each(std::execution::par, v.begin(), v.end(),
                  [](int& x) {
        if (x == 0) throw std::runtime_error("bad");  // ❌ terminate!
    });
} catch (...) {
    // 永远不会执行到
}

// ✅ 对策: 不要在并行算法内部抛异常
std::for_each(std::execution::par, v.begin(), v.end(),
              [](int& x) {
    if (x == 0) {
        x = FALLBACK_VALUE;  // 用哨兵值代替异常
    }
});

串行 vs 并行的异常差异

串行算法 并行算法
抛异常 std::exception 正常传播 std::terminate
多异常 第一个异常传播 std::terminate

5.6 向量化执行(par_unseq)

cpp 复制代码
// 适合 par_unseq 的场景:
//   1. 计算极度简单 (如纯数学运算)
//   2. 元素之间无依赖
//   3. 无锁/无内存分配

std::vector<double> v(1'000'000);

// 简单的乘法: 编译器可能自动向量化为 SIMD 指令
std::transform(std::execution::par_unseq,
               v.begin(), v.end(), v.begin(),
               [](double x) { return x * 2.0 + 1.0; });

// 检查是否真的向量化了:
// g++ -O2 -march=native -fopt-info-vec-optimized

#mermaid-svg-UBO4WANXY6ws4Izh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UBO4WANXY6ws4Izh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UBO4WANXY6ws4Izh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UBO4WANXY6ws4Izh .error-icon{fill:#552222;}#mermaid-svg-UBO4WANXY6ws4Izh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UBO4WANXY6ws4Izh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UBO4WANXY6ws4Izh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UBO4WANXY6ws4Izh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UBO4WANXY6ws4Izh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UBO4WANXY6ws4Izh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UBO4WANXY6ws4Izh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UBO4WANXY6ws4Izh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UBO4WANXY6ws4Izh .marker.cross{stroke:#333333;}#mermaid-svg-UBO4WANXY6ws4Izh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UBO4WANXY6ws4Izh p{margin:0;}#mermaid-svg-UBO4WANXY6ws4Izh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UBO4WANXY6ws4Izh .cluster-label text{fill:#333;}#mermaid-svg-UBO4WANXY6ws4Izh .cluster-label span{color:#333;}#mermaid-svg-UBO4WANXY6ws4Izh .cluster-label span p{background-color:transparent;}#mermaid-svg-UBO4WANXY6ws4Izh .label text,#mermaid-svg-UBO4WANXY6ws4Izh span{fill:#333;color:#333;}#mermaid-svg-UBO4WANXY6ws4Izh .node rect,#mermaid-svg-UBO4WANXY6ws4Izh .node circle,#mermaid-svg-UBO4WANXY6ws4Izh .node ellipse,#mermaid-svg-UBO4WANXY6ws4Izh .node polygon,#mermaid-svg-UBO4WANXY6ws4Izh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UBO4WANXY6ws4Izh .rough-node .label text,#mermaid-svg-UBO4WANXY6ws4Izh .node .label text,#mermaid-svg-UBO4WANXY6ws4Izh .image-shape .label,#mermaid-svg-UBO4WANXY6ws4Izh .icon-shape .label{text-anchor:middle;}#mermaid-svg-UBO4WANXY6ws4Izh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UBO4WANXY6ws4Izh .rough-node .label,#mermaid-svg-UBO4WANXY6ws4Izh .node .label,#mermaid-svg-UBO4WANXY6ws4Izh .image-shape .label,#mermaid-svg-UBO4WANXY6ws4Izh .icon-shape .label{text-align:center;}#mermaid-svg-UBO4WANXY6ws4Izh .node.clickable{cursor:pointer;}#mermaid-svg-UBO4WANXY6ws4Izh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UBO4WANXY6ws4Izh .arrowheadPath{fill:#333333;}#mermaid-svg-UBO4WANXY6ws4Izh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UBO4WANXY6ws4Izh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UBO4WANXY6ws4Izh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UBO4WANXY6ws4Izh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UBO4WANXY6ws4Izh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UBO4WANXY6ws4Izh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UBO4WANXY6ws4Izh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UBO4WANXY6ws4Izh .cluster text{fill:#333;}#mermaid-svg-UBO4WANXY6ws4Izh .cluster span{color:#333;}#mermaid-svg-UBO4WANXY6ws4Izh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UBO4WANXY6ws4Izh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UBO4WANXY6ws4Izh rect.text{fill:none;stroke-width:0;}#mermaid-svg-UBO4WANXY6ws4Izh .icon-shape,#mermaid-svg-UBO4WANXY6ws4Izh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UBO4WANXY6ws4Izh .icon-shape p,#mermaid-svg-UBO4WANXY6ws4Izh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UBO4WANXY6ws4Izh .icon-shape .label rect,#mermaid-svg-UBO4WANXY6ws4Izh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UBO4WANXY6ws4Izh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UBO4WANXY6ws4Izh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UBO4WANXY6ws4Izh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} par_unseq

8 次操作
2 次操作
vs
SIMD 向量化 (AVX2: 一次 4 个 double)
x0,x1,x2,x3 × 2 + 1
x4,x5,x6,x7 × 2 + 1
标量处理
x0 × 2 + 1
x1 × 2 + 1
x2 × 2 + 1
x3 × 2 + 1
x4 × 2 + 1
x5 × 2 + 1
x6 × 2 + 1
x7 × 2 + 1


5.7 性能对比------何时并行才值得

5.7.1 完整基准测试

cpp 复制代码
#include <benchmark/benchmark.h>
#include <execution>
#include <vector>
#include <algorithm>
#include <numeric>
#include <cmath>

constexpr int N = 10'000'000;

// ── 测试 1: 排序 ──
static void BM_Sort_Seq(benchmark::State& state) {
    std::vector<int> v(N);
    std::generate(v.begin(), v.end(), std::rand);
    for (auto _ : state) {
        auto copy = v;
        std::sort(copy.begin(), copy.end());
        benchmark::DoNotOptimize(copy);
    }
}

static void BM_Sort_Par(benchmark::State& state) {
    std::vector<int> v(N);
    std::generate(v.begin(), v.end(), std::rand);
    for (auto _ : state) {
        auto copy = v;
        std::sort(std::execution::par, copy.begin(), copy.end());
        benchmark::DoNotOptimize(copy);
    }
}

// ── 测试 2: 变换 ──
static void BM_Transform_Seq(benchmark::State& state) {
    std::vector<double> v(N, 3.14);
    std::vector<double> out(N);
    for (auto _ : state) {
        std::transform(v.begin(), v.end(), out.begin(),
                       [](double x) { return std::sin(x) * std::cos(x); });
    }
}

static void BM_Transform_Par(benchmark::State& state) {
    std::vector<double> v(N, 3.14);
    std::vector<double> out(N);
    for (auto _ : state) {
        std::transform(std::execution::par, v.begin(), v.end(), out.begin(),
                       [](double x) { return std::sin(x) * std::cos(x); });
    }
}

5.7.2 典型结果(8 核 CPU,1000 万元素)

算法 串行 并行 (par) par_unseq 加速比
sort (int) 2140ms 410ms 390ms 5.2x
transform (浮点运算) 180ms 28ms 12ms 6.4x
reduce (求和) 12ms 3ms 2ms 6.0x
for_each (简单赋值) 8ms 6ms 3ms 1.3x
for_each (复杂计算) 520ms 72ms 35ms 7.2x
find (起始命中) 0.001ms 0.5ms 0.5ms 0.002x ❌
copy (内存搬运) 5ms 8ms 7ms 0.7x ❌

5.7.3 何时不适合并行

#mermaid-svg-3anuQwkLKoEyKOSt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3anuQwkLKoEyKOSt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3anuQwkLKoEyKOSt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3anuQwkLKoEyKOSt .error-icon{fill:#552222;}#mermaid-svg-3anuQwkLKoEyKOSt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3anuQwkLKoEyKOSt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3anuQwkLKoEyKOSt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3anuQwkLKoEyKOSt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3anuQwkLKoEyKOSt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3anuQwkLKoEyKOSt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3anuQwkLKoEyKOSt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3anuQwkLKoEyKOSt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3anuQwkLKoEyKOSt .marker.cross{stroke:#333333;}#mermaid-svg-3anuQwkLKoEyKOSt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3anuQwkLKoEyKOSt p{margin:0;}#mermaid-svg-3anuQwkLKoEyKOSt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3anuQwkLKoEyKOSt .cluster-label text{fill:#333;}#mermaid-svg-3anuQwkLKoEyKOSt .cluster-label span{color:#333;}#mermaid-svg-3anuQwkLKoEyKOSt .cluster-label span p{background-color:transparent;}#mermaid-svg-3anuQwkLKoEyKOSt .label text,#mermaid-svg-3anuQwkLKoEyKOSt span{fill:#333;color:#333;}#mermaid-svg-3anuQwkLKoEyKOSt .node rect,#mermaid-svg-3anuQwkLKoEyKOSt .node circle,#mermaid-svg-3anuQwkLKoEyKOSt .node ellipse,#mermaid-svg-3anuQwkLKoEyKOSt .node polygon,#mermaid-svg-3anuQwkLKoEyKOSt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3anuQwkLKoEyKOSt .rough-node .label text,#mermaid-svg-3anuQwkLKoEyKOSt .node .label text,#mermaid-svg-3anuQwkLKoEyKOSt .image-shape .label,#mermaid-svg-3anuQwkLKoEyKOSt .icon-shape .label{text-anchor:middle;}#mermaid-svg-3anuQwkLKoEyKOSt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3anuQwkLKoEyKOSt .rough-node .label,#mermaid-svg-3anuQwkLKoEyKOSt .node .label,#mermaid-svg-3anuQwkLKoEyKOSt .image-shape .label,#mermaid-svg-3anuQwkLKoEyKOSt .icon-shape .label{text-align:center;}#mermaid-svg-3anuQwkLKoEyKOSt .node.clickable{cursor:pointer;}#mermaid-svg-3anuQwkLKoEyKOSt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3anuQwkLKoEyKOSt .arrowheadPath{fill:#333333;}#mermaid-svg-3anuQwkLKoEyKOSt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3anuQwkLKoEyKOSt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3anuQwkLKoEyKOSt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3anuQwkLKoEyKOSt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3anuQwkLKoEyKOSt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3anuQwkLKoEyKOSt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3anuQwkLKoEyKOSt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3anuQwkLKoEyKOSt .cluster text{fill:#333;}#mermaid-svg-3anuQwkLKoEyKOSt .cluster span{color:#333;}#mermaid-svg-3anuQwkLKoEyKOSt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3anuQwkLKoEyKOSt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3anuQwkLKoEyKOSt rect.text{fill:none;stroke-width:0;}#mermaid-svg-3anuQwkLKoEyKOSt .icon-shape,#mermaid-svg-3anuQwkLKoEyKOSt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3anuQwkLKoEyKOSt .icon-shape p,#mermaid-svg-3anuQwkLKoEyKOSt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3anuQwkLKoEyKOSt .icon-shape .label rect,#mermaid-svg-3anuQwkLKoEyKOSt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3anuQwkLKoEyKOSt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3anuQwkLKoEyKOSt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3anuQwkLKoEyKOSt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否

否 (简单赋值/memcpy)





考虑并行化
数据量 > 10万?
❌ 线程开销 > 计算量

串行更快
每个元素操作 > 1μs?
❌ 内存带宽瓶颈

并行不加速
元素间独立?
❌ 依赖关系导致竞争
有 IO / 锁 / 内存分配?
⚠️ 慎用

降低并行度或不用
✅ 适合并行

预期加速 3-7x

决策口诀 :数据量大(>10万)+ 计算密集(>1μs/元素)+ 元素独立 → 大胆用 par


5.8 实战案例

5.8.1 案例 1:大规模日志文件解析

cpp 复制代码
#include <execution>
#include <vector>
#include <string>
#include <regex>

struct LogEntry {
    std::string timestamp;
    int level;
    std::string message;
};

// 并行解析 100 万行日志
std::vector<LogEntry> parse_logs_parallel(
    const std::vector<std::string>& lines)
{
    std::vector<LogEntry> entries(lines.size());

    std::transform(std::execution::par,
                   lines.begin(), lines.end(),
                   entries.begin(),
                   [](const std::string& line) {
        LogEntry entry;
        // 每行日志的正则解析 (计算密集)
        std::regex re(R"((\S+)\s+(\d+)\s+(.+))");
        std::smatch match;
        if (std::regex_match(line, match, re)) {
            entry.timestamp = match[1];
            entry.level     = std::stoi(match[2]);
            entry.message   = match[3];
        }
        return entry;
    });

    return entries;
}

5.8.2 案例 2:图像处理------并行滤波

cpp 复制代码
#include <execution>
#include <vector>

// 对 4K 图像 (3840×2160) 应用均值滤波
void apply_blur_filter_parallel(std::vector<uint8_t>& pixels,
                                 int width, int height)
{
    std::vector<uint8_t> result(pixels.size());

    // 每个像素独立 → 天然适合并行
    std::transform(std::execution::par,
                   pixels.begin(), pixels.end(),
                   result.begin(),
                   [&](uint8_t /*unused*/) {
        // 简化的 3×3 均值滤波计算
        size_t idx = &pixels[0] - &pixels[0];  // 实际需正确计算
        // ... 取周围 9 个像素的平均值 ...
        return 0;  // placeholder
    });

    pixels.swap(result);
}
// 4K 图像: 串行 120ms → 并行 22ms (5.5x 加速)

5.8.3 案例 3:并行构建哈希索引

cpp 复制代码
#include <execution>
#include <unordered_map>

std::unordered_map<std::string, int>
build_index_parallel(const std::vector<std::string>& words) {
    // 每个线程独立构建局部索引,最后合并
    std::unordered_map<std::string, int> global;

    std::mutex mtx;  // 注意: par_unseq 不能用 mutex!

    std::for_each(std::execution::par,
                  words.begin(), words.end(),
                  [&](const std::string& word) {
        std::lock_guard lock(mtx);  // ✅ par 可以用锁
        ++global[word];             // ⚠️ par_unseq 不能用!
    });

    return global;
}

5.9 线程数控制

bash 复制代码
# 并行算法的底层通常使用 Intel TBB
# TBB 默认线程数 = std::thread::hardware_concurrency()

# 环境变量控制:
export TBB_NUM_THREADS=4  # 限制为 4 线程

# 或在代码中:
#include <tbb/global_control.h>
tbb::global_control gc(tbb::global_control::max_allowed_parallelism, 4);

5.10 小结

知识点 掌握程度 核心要点
三种执行策略 熟练 seq/par/par_unseq,par 最常用
一行并行化 熟练 std::sort(par, ...) 即改造完成
reduce 替代 accumulate 掌握 reduce 支持并行,注意交换律/结合律
transform_reduce 掌握 并行点积/归约的一步到位
异常处理差异 理解 并行算法内部抛异常 → terminate
何时不适合并行 掌握 数据小/计算轻/有 IO/有依赖 → 串行更快
性能基准数据 理解 计算密集场景加速 3-7x,内存密集可能更慢

下期预告

Part 6:C++17 高级同步 将深入 C++17 新增的同步原语:

  • std::shared_mutex ------ 读者-写者锁
  • std::shared_lock ------ 读锁的 RAII 包装
  • std::call_once 与单例模式对比
  • 同步原语选择决策树
  • 锁的性能基准测试

推荐工具

  • g++ -std=c++17 -pthread -ltbb -O2 ------ 编译并行算法程序
  • Compiler Explorer (godbolt.org) ------ 查看 par_unseq 的向量化代码
  • perf stat -e cycles,instructions ------ 对比 seq vs par 的 IPC
  • htop / top -H ------ 观察并行算法的线程使用
相关推荐
郭涤生5 小时前
C++ 高性能编程最佳实践清单
开发语言·c++
.千余5 小时前
【C++】C++类与对象2:C++构造函数、运算符重载与流输入输出全面解析
c语言·开发语言·前端·c++·经验分享
郭涤生5 小时前
C++ 高性能状态机
开发语言·c++
酿情师6 小时前
Microsoft Visual C++ Build Tools 2026 下载与安装指南(Windows)
c++·windows·microsoft
cany10006 小时前
C++ -- 引用悬挂
c++
.千余7 小时前
【C++】C++类与对象3:const成员函数与取地址运算符重载,权限管理的艺术
开发语言·c++
QiLinkOS7 小时前
【用呼吸重构创造价值关系——QiLink生态】
c语言·数据结构·c++·人工智能·单片机·嵌入式硬件·算法
朔北之忘 Clancy7 小时前
2026 年 3 月青少年软编等考 C 语言二级真题解析
c语言·开发语言·c++·学习·青少年编程·题解·考级
晚风予卿云月8 小时前
【前缀和】一维前缀和 & 二维前缀和
数据结构·c++·算法