c++ - OpenMP 并行循环比常规循环慢得多

标签 c++ performance parallel-processing openmp

整个程序已缩减为一个简单的测试:

    const int loops = 1e10;
    int j[4] = { 1, 2, 3, 4 };
    time_t time = std::time(nullptr);
    for (int i = 0; i < loops; i++) j[i % 4] += 2;
    std::cout << std::time(nullptr) - time << std::endl;

    int k[4] = { 1, 2, 3, 4 };
    omp_set_num_threads(4);
    time = std::time(nullptr);
#pragma omp parallel for
    for (int i = 0; i < loops; i++) k[omp_get_thread_num()] += 2;
    std::cout << std::time(nullptr) - time << std::endl;

在第一种情况下,运行循环大约需要 3 秒,在第二种情况下,结果不一致,可能需要 4 - 9 秒。在启用一些优化(例如有利于速度和整个程序优化)的情况下,两个循环运行得更快,但第二个循环仍然明显较慢。我尝试在循环末尾添加屏障并显式将数组指定为共享,但这没有帮助。我设法使并行循环运行得更快的唯一情况是使循环为空。可能是什么问题?

Windows 10 x64,CPU Intel Core i5 10300H(4 核)

最佳答案

正如各种评论中已经指出的那样,您问题的症结是 false sharing 。事实上,你的例子是一个可以进行实验的典型案例。但是,您的代码中也存在不少问题,例如:

  • 您可能会在 loops 变量以及所有 jk 表中看到溢出;
  • 你的计时器并不是真正的最佳选择(诚然,在这种情况下,我的观点有点迂腐);
  • 您没有使用您计算的值,这使得编译器可以完全忽略各种计算;
  • 你的两个循环不等价,不会给出相同的结果;为了使其正确,我回到原来的 i%4 公式并添加了 schedule( static, 1) 子句。这不是正确的做法,但这只是为了在不使用正确的 reduction 子句的情况下获得预期结果。

然后,我重写了您的示例,并用我认为更好的解决错误共享问题的方法对其进行了增强:使用 reduction 子句。

#include <iostream>
#include <omp.h>

int main() {
    const long long loops = 1e10;
    long long j[4] = { 1, 2, 3, 4 };
    double time = omp_get_wtime();
    for ( long long i = 0; i < loops; i++ ) {
         j[i % 4] += 2;
    }
    std::cout << "sequential: " << omp_get_wtime() - time << std::endl;

    time = omp_get_wtime();
    long long k[4] = { 1, 2, 3, 4 };
    #pragma omp parallel for num_threads( 4 ) schedule( static, 1 )
    for ( long long i = 0; i < loops; i++ ) {
        k[i%4] += 2;
    }
    std::cout << "false sharing: " << omp_get_wtime() - time << std::endl;

    time = omp_get_wtime();
    long long l[4] = { 1, 2, 3, 4 };
    #pragma omp parallel for num_threads( 4 ) reduction( +: l[0:4] )
    for ( long long i = 0; i < loops; i++ ) {
        l[i%4] += 2;
    }
    std::cout << "reduction: " << omp_get_wtime() - time << std::endl;

    bool a = j[0]==k[0] && j[1]==k[1] && j[2]==k[2] && j[3]==k[3];
    bool b = j[0]==l[0] && j[1]==l[1] && j[2]==l[2] && j[3]==l[3];
    std::cout << "sanity check: " << a << " " << b << std::endl;

    return 0;
}

在我的笔记本电脑上编译和运行而无需优化:

$ g++ -O0 -fopenmp false.cc 
$ ./a.out 
sequential: 15.5384
false sharing: 47.1417
reduction: 4.7565
sanity check: 1 1

这足以说明 reduction 条款带来的改进。 现在,从编译器启用优化可以提供更轻松的画面:

$ g++ -O3 -fopenmp false.cc 
$ ./a.out 
sequential: 4.8414
false sharing: 4.10714
reduction: 2.10953
sanity check: 1 1

如果有的话,那就表明编译器非常善于避免当今的大多数错误共享。事实上,对于您最初的(错误的)k[omp_get_thread_num()],使用和不使用 reduction 子句没有时间差异,这表明编译器能够避免问题。

关于c++ - OpenMP 并行循环比常规循环慢得多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71767545/

相关文章:

performance - 了解 Clojure 转换器性能

sql - 提高PostgreSQL查询效率-一对多,Count为1

c# - 如果作为单独的任务启动,有没有办法取消 Parallel.ForEach 循环

php - MySQL 无法创建新线程(errno 12)

PHP MySQL并发,数据库中的最大项目数有限

python - 使用 dask.bag 与普通的 python 列表?

c++ - VC++中如何使用资源?

c++ - 整数递增使应用程序崩溃

C++ 命名空间和包含

c++ - 如何在 gcc 版本之间正确切换?