当前位置: 首页 > news >正文

【C++】transform, reduce, scan是什么意思?理解常用并行算法及其实现原理

文章目录

  • 从并发到并行
    • 并发和并行的区别
    • 因特尔开源的并行编程库:TBB
    • 基于 TBB 的版本:任务组
    • parallel_invoke
  • 并行循环
    • 时间复杂度(time-efficiency)与工作量复杂度(work-efficiency)
    • 映射(map)
    • parallel_for
    • 基于迭代器区间:parallel_for_each
    • 二维区间上的 for 循环:blocked_range2d
    • 三维区间上的 for 循环:blocked_range3d
    • 所有区间类型
  • 缩并与扫描
    • 缩并(reduce)
    • 并行缩并
  • 性能测试
  • 参考

从并发到并行

并发和并行的区别

运用多线程的方式和动机,一般分为两种。

并发:单核处理器,操作系统通过时间片调度算法,轮换着执行着不同的线程,看起来就好像是同时运行一样,其实每一时刻只有一个线程在运行。目的:异步地处理多个不同的任务,避免同步造成的阻塞。

并行:多核处理器,每个处理器执行一个线程,真正的同时运行。目的:将一个任务分派到多个核上,从而更快完成任务。

在这里插入图片描述

因特尔开源的并行编程库:TBB

在这里插入图片描述

  • link

安装 TBB

Ubuntu:
sudo apt-get install libtbb-dev
Arch Linux:
sudo pacman -S tbb
Windows:
.\vcpkg install tbb:x64-windows
Mac OS:
.\vcpkg install tbb:x64-macos
Other:
从源码构建安装,参考:https://blog.csdn.net/weixin_42973508/article/details/111681426

安装第三方库-包管理器

·Linux可以用系统自带的包管理器(如apt)安装C++包。
·>pacman-S fmt·Windows则没有自带的包管理器。因此可以用跨平台的vcpkg:
https://github.com/microsoft/vcpkg
·使用方法:下载vcpkg的源码,放到你的项目根目录
>vcpkg
·>cd vcpkgM
·>./bootstrap-vcpkg.bat
·>./vcpkg integrate install
·>./vcpkg install fmt:x64-windows
·>cd ..
·>cmake-B build -DCMAKE_TOOLCHAIN_FILE="%CD%/vcpkg/scripts/buildsystems/vcpkg.cmake"

在这里插入图片描述

基于 TBB 的版本:任务组

用一个任务组 tbb::task_group 启动多个任务,一个负责下载,一个负责和用户交互。并在主线程中等待该任务组里的任务全部执行完毕。

区别在于,一个任务不一定对应一个线程,如果任务数量超过CPU最大的线程数,会由 TBB 在用户层负责调度任务运行在多个预先分配好的线程,而不是由操作系统负责调度线程运行在多个物理核心。
在这里插入图片描述

parallel_invoke

在这里插入图片描述
在这里插入图片描述

并行循环

时间复杂度(time-efficiency)与工作量复杂度(work-efficiency)

对于并行算法,复杂度的评估则要分为两种:
时间复杂度:程序所用的总时间(重点)
工作复杂度:程序所用的计算量(次要)

这两个指标都是越低越好。时间复杂度决定了快慢,工作复杂度决定了耗电量。

  • 通常来说,工作复杂度 = 时间复杂度 * 核心数量
    1个核心工作一小时,4个核心工作一小时。时间复杂度一样,而后者工作复杂度更高。
    1个核心工作一小时,4个核心工作1/4小时。工作复杂度一样,而后者时间复杂度更低。

并行的主要目的是降低时间复杂度,工作复杂度通常是不变的。甚至有牺牲工作复杂度换取时间复杂度的情形。

并行算法的复杂度取决于数据量 n,还取决于线程数量 c,比如 O(n/c)。不过要注意如果线程数量超过了 CPU 核心数量,通常就无法再加速了,这就是为什么要买更多核的电脑。

也有一种说法,认为要用 c 趋向于无穷时的时间复杂度来衡量,比如 O(n/c) 应该变成 O(1)。

映射(map)

1个线程,独自处理8个元素的映射,花了8秒
用电量:1*8=8度电
在这里插入图片描述
结论:串行映射的时间复杂度为 O(n),工作复杂度为 O(n),其中 n 是元素个数

并行映射
4个线程,每人处理2个元素的映射,花了2秒
用电量:4*2=8度电
在这里插入图片描述

结论:并行映射的时间复杂度为 O(n/c),工作复杂度为 O(n),其中 c 是线程数量

parallel_for

在这里插入图片描述
在这里插入图片描述

基于迭代器区间:parallel_for_each

在这里插入图片描述

二维区间上的 for 循环:blocked_range2d

在这里插入图片描述

三维区间上的 for 循环:blocked_range3d

在这里插入图片描述

所有区间类型

在这里插入图片描述

缩并与扫描

缩并(reduce)

1个线程,依次处理8个元素的缩并,花了7秒
用电量:17=7度电
总用时:1
7=7秒

在这里插入图片描述

结论:串行缩并的时间复杂度为 O(n),工作复杂度为 O(n),其中 n 是元素个数

并行缩并

第一步、4个线程,每人处理2个元素的缩并,花了1秒
第二步、1个线程,独自处理4个元素的缩并,花了3秒
用电量:41+13=7度电
总用时:1+3=4秒
在这里插入图片描述
主要逻辑:把n个数的任务,分配个4个线程去做(用temp_res数组分别统计求和),按照每个线程处理[begin,end)之间的任务。最终将4个线程的值进行归并。

#include <iostream>
#include <tbb/task_group.h>  // TBB 并行任务组
#include <vector>
#include <cmath>             // 数学函数 sin()int main() {size_t n = 1<<26;        // 2²⁶ = 67,108,864float res = 0;           // 存储最终结果size_t maxt = 4;         // 线程数tbb::task_group tg;      // TBB 任务组std::vector<float> tmp_res(maxt);  // 存储每个线程的局部结果
for (size_t t = 0; t < maxt; t++) {size_t beg = t * n / maxt;  // 当前线程的起始索引size_t end = std::min(n, (t + 1) * n / maxt);  // 结束索引tg.run([&, t, beg, end] {  // 提交任务到线程池float local_res = 0;for (size_t i = beg; i < end; i++) {local_res += std::sin(i);  // 计算局部和}tmp_res[t] = local_res;  // 保存局部结果});
}分工逻辑:
4 个线程均分 n 个元素(每个线程处理约 16,777,216 个元素)。
tg.run() 将任务提交给 TBB 线程池异步执行。
tmp_res 存储每个线程的局部和。

合并局部结果

for (size_t t = 0; t < maxt; t++) {res += tmp_res[t];  // 汇总所有线程的结果
}
std::cout << res << std::endl;

结论:并行缩并的时间复杂度为 O(n/c+c),工作复杂度为 O(n),其中 n 是元素个数

类似加法分配律的思想,1+2+3+4,方法:((1+2)+3)+4或者(1+2)+(3+4)。类似这样的思想

性能测试

参考

  • 【C++】transform, reduce, scan是什么意思?理解常用并行算法及其实现原理
  • slide

相关文章:

  • [Andrej Karpathy_2] vibe coding | 大型语言模型的1960年代 | 自主性滑块
  • 【云桌面容器KasmVNC】如何关闭SSL使用HTTP
  • [Linux]从零开始的STM32MP157移植Ubuntu根文件系统教程
  • Linux软连接和硬连接
  • git 挑选:git cherry-pick
  • DeepSeek改写glaredb的示例实现自定义CLI界面程序
  • c# 比较两个list 之间元素差异
  • (五)神经网络
  • uni-app项目实战笔记26--uniapp实现富文本展示
  • MicroProfile的配置和MicroProfile健康
  • 设备树引入
  • 动态面板axure
  • 告别固定密钥!在单一账户下用 Cognito 实现 AWS CLI 的 MFA 单点登录
  • Spring Cloud Gateway 实战:网关配置与 Sentinel 限流详解
  • 零知开源——基于STM32F407VET6零知增强板的四路独立计时器
  • 快速掌握广告联盟APP开发全流程,短剧和游戏广告app
  • 全面拥抱vue3
  • kubectl get pod返回数据研究
  • InfluxDB 3 Core数据库管理指南:从概念到实操的完整流程
  • 机器学习开篇:算法分类与开发流程