簡體   English   中英

OpenMP 並行循環比常規循環慢得多

[英]OpenMP parallel loop much slower than regular loop

整個程序已經縮減為一個簡單的測試:

    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 秒。 啟用某些優化(例如有利於速度和整個程序優化)的兩個循環運行得更快,但第二個循環仍然慢得多。 我嘗試在循環末尾添加 barrier 並將數組明確指定為shared ,但這沒有幫助。 我設法使並行循環運行得更快的唯一情況是使循環為空。 可能是什么問題?

Windows 10 x64,CPU 英特爾酷睿 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條款沒有時間差異,表明編譯器能夠避免這個問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM