簡體   English   中英

OpenMP 減少容器元素

[英]OpenMP reduction on container elements

我有一個嵌套循環,外部迭代很少,內部迭代很多。 在內部循環中,我需要計算一個總和,所以我想使用 OpenMP 縮減。 外部循環在一個容器上,因此減少應該發生在該容器的一個元素上。 這是一個最小的人為示例:

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

int main(){
    constexpr int n { 128 };

    std::vector<int> vec (4, 0);
    for (unsigned int i {0}; i<vec.size(); ++i){

        /* this does not work */
        //#pragma omp parallel for reduction (+:vec[i])
        //for (int j=0; j<n; ++j)
        //  vec[i] +=j;

        /* this works */
        int* val { &vec[0] };
        #pragma omp parallel for reduction (+:val[i])
        for (int j=0; j<n; ++j)
            val[i] +=j;

        /* this is allowed, but looks very wrong. Produces wrong results
         * for std::vector, but on an Eigen type, it worked. */
        #pragma omp parallel for reduction (+:val[i])
        for (int j=0; j<n; ++j)
            vec[i] +=j;
    }
    for (unsigned int i=0; i<vec.size(); ++i) std::cout << vec[i] << " ";
    std::cout << "\n";

    return 0;
}

問題是,如果我將歸約子句寫為(+:vec[i]) ,我會得到錯誤'vec' does not have pointer or array type ,它的描述性足以找到解決方法。 但是,這意味着我必須引入一個新變量並稍微更改代碼邏輯,而且我發現看代碼應該做什么不太明顯。

我的主要問題是,是否有更好/更清潔/更標准的方法來編寫容器元素的縮減。

我還想知道上面代碼中顯示的第三種方式為什么以及如何工作。 我實際上正在使用Eigen庫,在其容器上該變體似乎工作得很好(雖然還沒有廣泛測試它),但在std::vector上,它產生的結果介於零和實際結果之間(8128) . 我認為它應該可以工作,因為vec[i]val[i]都應該評估為取消引用相同的地址。 但很可惜,顯然不是。

我正在使用 OpenMP 4.5 和 gcc 9.3.0。

我將分三個部分回答你的問題:

1.在上面的示例中使用std::vec執行 OpenMP 縮減的最佳方法是什么?

i) 使用您的方法,即創建一個指針int* val { &vec[0] };

ii) 聲明一個新的共享變量,如 @1201ProgramAlarm 已回答。

iii)聲明一個用戶定義的減少(這在您的簡單情況下並不適用,但請參閱下面的 3. 以獲得更有效的模式)。

2. 為什么第三個循環不起作用,為什么它與 Eigen 一起起作用?

與前面的答案狀態一樣,您告訴 OpenMP 對 memory 地址 X 執行歸約和,但您正在對 memory 地址 Y 執行加法,這意味着歸約聲明被忽略,並且您的加法受到通常的線程競爭條件的影響。

您並沒有真正為您的 Eigen 冒險提供太多細節,但這里有一些可能的解釋:

i)您並沒有真正使用多個線程(檢查n = Eigen::nbThreads( )

ii) 您沒有禁用 Eigen 自己的並行性,這可能會破壞您自己對 OpenMP 的使用,例如EIGEN_DONT_PARALLELIZE編譯器指令。

iii)存在競爭條件,但您沒有看到它,因為 Eigen 操作需要更長的時間,您使用的線程數量較少,並且只寫入少量值 => 較少出現的線程相互干擾以產生錯誤的結果。

3. 我應該如何使用 OpenMP 並行化這個場景(技術上不是你明確提出的問題)?

您應該同時並行化兩者,而不是僅並行化內部循環。 您擁有的序列號越少越好。 在這種情況下,每個線程都有自己的vec向量的私有副本,在所有元素都被各自的線程求和后,該副本會減少。 此解決方案最適合您提供的示例,但如果您使用非常大的向量和非常多的線程(或 RAM 非常有限),則可能會遇到 RAM 問題。

#pragma omp parallel for collapse(2) reduction(vsum : vec)
for (unsigned int i {0}; i<vec.size(); ++i){
    for (int j = 0; j < n; ++j) {
        vec[i] += j;
    }
}

其中 vsum 是用戶定義的縮減,即

#pragma omp declare reduction(vsum : std::vector<int> : std::transform(omp_out.begin(), omp_out.end(), omp_in.begin(), omp_out.begin(), std::plus<int>())) initializer(omp_priv = decltype(omp_orig)(omp_orig.size()))

在你使用它的 function 之前聲明減少,你會很好 go

對於第二個示例,與其存儲指針然后總是訪問相同的元素,不如使用局部變量:

    int val = vec[i];
    #pragma omp parallel for reduction (+:val)
    for (int j=0; j<n; ++j)
        val +=j;
    vec[i] = val;

對於第 3 個循環,我懷疑問題是因為reduction子句命名了一個變量,但是您永遠不會在循環中按該名稱更新該變量,因此編譯器沒有看到要減少的任何內容。 使用Eigen可能會使代碼分析起來更加復雜,從而導致循環工作。

暫無
暫無

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

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