OpenMP 减慢了计算速度

OpenMP slows down computations

我正在尝试使用 OpenMP 并行化一个简单的循环。下面是我的代码:

#include <iostream>
#include <omp.h>
#include <time.h>
#define SIZE 10000000

float calculate_time(clock_t start, clock_t end) {
    return (float) ((end - start) / (double) CLOCKS_PER_SEC) * 1000;
}

void openmp_test(double * x, double * y, double * res, int threads){
    clock_t start, end;
    std::cout <<  std::endl << "OpenMP, " << threads << " threads" << std::endl;
    start = clock();
    #pragma omp parallel for num_threads(threads)
    for(int i = 0; i < SIZE; i++){
        res[i] = x[i] * y[i];
    }
    end = clock();

    for(int i = 1; i < SIZE; i++){
        res[0] += res[i];
    }
    std::cout << "time: " << calculate_time(start, end) << std::endl;
    std::cout << "result: " << res[0] << std::endl;
}

int main() {

    double *dbl_x = new double[SIZE];
    double *dbl_y = new double[SIZE];
    double *res = new double[SIZE];
    for(int i = 0; i < SIZE; i++){
        dbl_x[i] = i % 1000;
        dbl_y[i] = i % 1000;
    }

    openmp_test(dbl_x, dbl_y, res, 1);
    openmp_test(dbl_x, dbl_y, res, 1);
    openmp_test(dbl_x, dbl_y, res, 2);
    openmp_test(dbl_x, dbl_y, res, 4);
    openmp_test(dbl_x, dbl_y, res, 8);

    delete [] dbl_x;
    delete [] dbl_y;
    delete [] res;
    return 0;
}

我编译如下

g++ -O3 -fopenmp main.cpp -o ompTest

然而,运行 在 Core-i7 上进行测试后,我得到以下结果:

OpenMP,1 个线程 时间:31.468 结果:3.32834e+12

OpenMP,1 个线程 时间:18.663 结果:3.32834e+12

OpenMP,2 个线程 时间:34.393 结果:3.32834e+12

OpenMP,4 个线程 时间:56.31 结果:3.32834e+12

OpenMP,8 个线程 时间:108.54 结果:3.32834e+12

我不明白我做错了什么?为什么 OpenMP 会减慢计算速度?

另外,为什么第一个结果比第二个结果慢得多(均使用 1 个 omp 线程)?

我的测试环境:Core i7-4702MQ CPU @ 2.20GHz, Ubuntu 18.04.2 LTS, g++ 7.4.0.

目前,您创建了线程,但您为它们分配了相同的工作。

我想,你忘记了pragma中的"for",它使线程将循环分成几部分。

    #pragma omp parallel for num_threads(threads)

这里至少发生了两件事。

  1. clock() 测量经过的 处理器 时间,这可以看作是对已执行工作量的测量,而你想测量经过的时间 时间。见 OpenMP time and clock() calculates two different results.

  2. 并行程序中的总处理器时间应多于可比较的串行程序,因为并行化会增加开销。线程越多,开销越大,因此每个添加线程的速度提升会随着线程的增加而降低,甚至可能变为负值。

与您的代码的这个变体相比,它实现了一种更合适的方法来测量经过的墙时间:

float calculate_time(struct timespec start, struct timespec end) {
    long long start_nanos = start.tv_sec * 1000000000LL + start.tv_nsec;
    long long end_nanos = end.tv_sec * 1000000000LL + end.tv_nsec;
    return (end_nanos - start_nanos) * 1e-6f;
}

void openmp_test(double * x, double * y, double * res, int threads){
    struct timespec start, end;
    std::cout <<  std::endl << "OpenMP, " << threads << " threads" << std::endl;
    clock_gettime(CLOCK_MONOTONIC, &start);

    #pragma omp parallel num_threads(threads)
    for(int i = 0; i < SIZE; i++){
        res[i] = x[i] * y[i];
    }

    clock_gettime(CLOCK_MONOTONIC, &end);

    for(int i = 1; i < SIZE; i++){
        res[0] += res[i];
    }
    std::cout << "time: " << calculate_time(start, end) << std::endl;
    std::cout << "result: " << res[0] << std::endl;
}

我的结果是

OpenMP, 1 threads
time: 92.5535
result: 3.32834e+12

OpenMP, 2 threads
time: 56.128
result: 3.32834e+12

OpenMP, 4 threads
time: 59.8112
result: 3.32834e+12

OpenMP, 8 threads
time: 78.9066
result: 3.32834e+12

请注意两个线程的测量时间如何减少了大约一半,但添加更多内核并没有太大改善,最终开始趋向于回到 single-thread 时间。* 这展示了在我的 four-core、eight-hyperthread 机器上同时执行更多工作的竞争效应,以及与协调更多线程相关的开销和资源争用增加。

底线:在任务中投入更多线程并不一定能让你更快地获得结果,而且它很少能让你获得与线程数成比例的加速。


* 全面披露:我 cherry-picked 这些特定结果来自多次运行的结果。所有都显示出相似的趋势,但趋势在这一个中特别明显 - 因此可能被过分强调了。