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

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

基本思想

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

实现代码

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

#include 
#include 
#include 
#include 
#include 
#include 

// 并行版本的 std::partial_sum
template
OutputIterator parallel_partial_sum(Iterator first, Iterator last, OutputIterator result) {
    const unsigned long length = std::distance(first, last);

    // 如果没有元素,直接返回 result
    if (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 threads(num_threads - 1);
    std::vector 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 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 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 input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::vector output(input.size());

    // 使用并行版本的 std::partial_sum
    parallel_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 来存储所有线程。
    • 每个线程独立计算其子范围的前缀和,并将最后一个元素的前缀和存储在 block_sums 中。为了确保 block_sums 的访问是线程安全的,我们使用了 std::mutex
  3. 合并结果

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

应用

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

  1. 数值计算

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

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

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

总结

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

你可能感兴趣的:(并发编程学习,C++,算法,并发编程,c++)