C++并发编程之std::partial_sum的并行版本

ops/2025/1/16 13:05:49/

在C++中,std::partial_sum 是一个用于计算前缀和的算法,它将输入范围中的每个元素替换为其前缀和。为了提高性能,我们可以设计并实现一个并行版本的 std::partial_sum,以便在多核处理器上并行执行前缀和计算。基本思想是将输入范围划分为多个子范围,每个子范围由一个单独的线程处理,并在所有线程完成后进行合并。

基本思想

  1. 任务划分:将输入范围中的元素划分为多个子范围,每个子范围由一个线程处理。
  2. 线程执行:每个线程独立计算其子范围的前缀和。为了确保最终结果的正确性,每个子范围的前缀和计算需要考虑到前一个子范围的最后一个元素的前缀和。
  3. 合并结果:在所有线程完成其任务后,主线程负责合并各个子范围的前缀和结果,确保整个输入范围的前缀和计算是正确的。

实现代码

我们可以使用 C++11 的 std::thread 来实现并行版本的 std::partial_sum。为了简化实现,我们可以使用 std::vector 来管理线程,并使用 std::mutex 来确保对共享数据的访问是线程安全的。

#include <iostream>
#include <vector>
#include <thread>
#include <algorithm>
#include <iterator>
#include <mutex>// 并行版本的 std::partial_sum
template<typename Iterator, typename OutputIterator>
OutputIterator parallel_partial_sum(Iterator first, Iterator last, OutputIterator result) {const unsigned long length = std::distance(first, last);// 如果没有元素,直接返回 resultif (length == 0) {return result;}// 获取系统支持的并发线程数const unsigned long max_threads = std::thread::hardware_concurrency();const unsigned long num_threads = std::min(max_threads != 0 ? max_threads : 2, length);// 每个线程处理的元素数量const unsigned long block_size = length / num_threads;std::vector<std::thread> threads(num_threads - 1);std::vector<typename Iterator::value_type> block_sums(num_threads, 0);std::mutex block_sums_mutex;// 启动线程for (unsigned long i = 0; i < num_threads - 1; ++i) {Iterator block_start = first + i * block_size;Iterator block_end = block_start + block_size;threads[i] = std::thread([block_start, block_end, result, i, &block_sums, &block_sums_mutex, block_size]() {*result = *block_start;typename Iterator::value_type sum = *block_start;for (Iterator it = block_start + 1; it != block_end; ++it) {sum += *it;*++result = sum;}std::lock_guard<std::mutex> lock(block_sums_mutex);block_sums[i] = sum;});}// 主线程处理最后一个块Iterator block_start = first + (num_threads - 1) * block_size;Iterator block_end = last;*result = *block_start;typename Iterator::value_type sum = *block_start;for (Iterator it = block_start + 1; it != block_end; ++it) {sum += *it;*++result = sum;}std::lock_guard<std::mutex> lock(block_sums_mutex);block_sums[num_threads - 1] = sum;// 等待所有线程完成std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));// 合并结果OutputIterator final_result = result;for (unsigned long i = 1; i < num_threads; ++i) {*final_result += block_sums[i - 1];++final_result;}for (unsigned long i = 1; i < num_threads; ++i) {for (unsigned long j = 1; j < block_size; ++j) {*final_result += block_sums[i - 1];++final_result;}}return result + std::distance(first, last);
}int main() {std::vector<int> input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};std::vector<int> output(input.size());// 使用并行版本的 std::partial_sumparallel_partial_sum(input.begin(), input.end(), output.begin());// 输出结果for (const auto& value : output) {std::cout << value << " ";}std::cout << std::endl;return 0;
}

代码说明

  1. 任务划分

    • length 是输入范围中的元素总数。
    • max_threads 是系统支持的并发线程数,num_threads 是我们实际使用的线程数(不超过元素数量)。
    • block_size 是每个线程处理的元素数量。
  2. 线程执行

    • 我们创建了一个 std::vector<std::thread> 来存储所有线程。
    • 每个线程独立计算其子范围的前缀和,并将最后一个元素的前缀和存储在 block_sums 中。为了确保 block_sums 的访问是线程安全的,我们使用了 std::mutex
  3. 合并结果

    • 主线程通过 std::thread::join 等待所有子线程完成。
    • 主线程遍历 block_sums,对每个子范围的前缀和进行调整,确保整个输入范围的前缀和计算是正确的。

应用

并行版本的 std::partial_sum 可以用于需要快速计算大规模数据前缀和的场景,例如:

  1. 数值计算

    • 例如,在科学计算中计算累积和、累积乘积等。
  2. 数据处理

    • 例如,在处理时间序列数据时,计算某个时间窗口内的累计值。
  3. 机器学习

    • 例如,在训练模型时,计算某个批次数据的累计损失。

总结

通过实现并行版本的 std::partial_sum,我们可以在多核处理器上并行执行前缀和计算,从而提高程序的性能。代码中展示了如何将输入范围中的元素划分为多个子范围,并使用多个线程分别处理这些子范围。这种技术可以广泛应用于需要高效计算大规模数据前缀和的场景。


http://www.ppmy.cn/ops/150560.html

相关文章

科技快讯 | 华为余承东2025新年信;教育部拟同意设置福耀科技大学等本科院校;我国成功发射一箭10星

四部门&#xff1a;畅通数据采集、标注、人工智能应用产业 财联社1月13日电&#xff0c;国家发展改革委等四部门发布《关于促进数据标注产业高质量发展的实施意见》。其中提出&#xff0c;畅通数据采集、标注、人工智能应用产业链&#xff0c;推动数据标注产业上下游协同发展。…

VSCode使用纪要

1、常用快捷键 1&#xff09;注释 ctrl? 单行注释&#xff0c; altshifta 块注释&#xff0c; 个人测试&#xff0c;ctrl? 好像也能块注释 2&#xff09;开多个项目 可以先开一个新窗口&#xff0c;再新窗口打开另一个项目&#xff0c;这时就是同时打开多个项目了。 打开…

美化IDE之修改IDEA启动界面logo图片

1&#xff0c;关闭运行的IDEA 2&#xff0c;在IDEA安装目录下的lib里找到app.jar或者platform-impl.jar(因为不同版本会有区别)复制出来 3&#xff0c;解压&#xff0c;找到两个图片idea_logo.png和idea_logo2x.png&#xff0c;分辨率一个为640x400 一个是1280x800 4&#xff0…

Spring Boot中如何处理跨域请求(CORS)

在Spring Boot中&#xff0c;处理跨域请求&#xff08;CORS, Cross-Origin Resource Sharing&#xff09;通常有几种方法。可以通过全局配置、控制器级别的配置或者方法级别的配置来实现。以下是三种常见的方式&#xff1a; 1. 全局配置 CORS 你可以在全局配置中处理跨域请求…

[Linux]Docker快速上手操作教程

前言 以下命令并不是docker的所有&#xff0c;仅涉及日常使用时最最常用的命令。 目的之一时给入门的朋友熟悉学习&#xff0c;其二时我自己偶尔使用时备忘。 一、概念 简单介绍下docker的相关概念&#xff1a; 镜像&#xff1a;Docker 镜像是一个轻量级、可执行的独立软件…

蓝桥杯_B组_省赛_2022(用作博主自己学习)

题目链接算法11.九进制转十进制 - 蓝桥云课 进制转换 21.顺子日期 - 蓝桥云课 时间与日期 31.刷题统计 - 蓝桥云课 时间与日期 41.修剪灌木 - 蓝桥云课 思维 51.X 进制减法 - 蓝桥云课 贪心 61.统计子矩阵 - 蓝桥云课 二维前缀和 71.积木画 - 蓝桥云课 动态规划 82.扫雷 - 蓝桥…

下载导出Tomcat上的excle文档,浏览器上显示下载

目录 1.前端2.Tomcat服务器内配置3.在Tomcat映射的文件内放置文件4.重启Tomcat&#xff0c;下载测试 1.前端 function downloadFile() {let pictureSourceServer "http://192.168.1.1:8080/downFile/";let fileName "测试文档.xlsx";let fileURL pictu…

书生大模型基础岛第四关

任务要求1&#xff08;必做&#xff0c;参考readme_api.md&#xff09;&#xff1a;基于 LlamaIndex 构建自己的 RAG 知识库&#xff0c;寻找一个问题 A 在使用 LlamaIndex 之前 浦语 API 不会回答&#xff0c;借助 LlamaIndex 后 浦语 API 具备回答 A 的能力&#xff0c;截图保…