簡體   English   中英

如何加速此循環(在C中)?

[英]How can I speed-up this loop (in C)?

我正在嘗試並行化C中的卷積函數。這是原始函數,它會卷積兩個64位浮點數組:

void convolve(const Float64 *in1,
              UInt32 in1Len,
              const Float64 *in2,
              UInt32 in2Len,
              Float64 *results)
{
    UInt32 i, j;

    for (i = 0; i < in1Len; i++) {
        for (j = 0; j < in2Len; j++) {
            results[i+j] += in1[i] * in2[j];
        }
    }
}

為了允許並發(沒有信號量),我創建了一個函數來計算results數組中特定位置的results

void convolveHelper(const Float64 *in1,
                    UInt32 in1Len,
                    const Float64 *in2,
                    UInt32 in2Len,
                    Float64 *result,
                    UInt32 outPosition)
{
    UInt32 i, j;

    for (i = 0; i < in1Len; i++) {
        if (i > outPosition)
            break;
        j = outPosition - i;
        if (j >= in2Len)
            continue;
        *result += in1[i] * in2[j];
    }
}

問題是,使用convolveHelper將代碼減慢約3.5倍(在單個線程上運行時)。

關於如何在保持線程安全的同時加快convolveHelper任何想法?

時域中的卷積在傅立葉域中成為乘法。 我建議你抓住一個快速FFT庫(如FFTW )並使用它。 你將從O(n ^ 2)到O(n log n)。

算法優化幾乎總是優於微優化。

可能有幫助的最明顯的事情是預先計算循環的起始和結束索引,並刪除ij上的額外測試(及其相關的跳轉)。 這個:

for (i = 0; i < in1Len; i++) {
   if (i > outPosition)
     break;
   j = outPosition - i;
   if (j >= in2Len)
     continue;
   *result += in1[i] * in2[j];
}

可以改寫為:

UInt32 start_i = (in2Len < outPosition) ? outPosition - in2Len + 1 : 0;
UInt32 end_i = (in1Len < outPosition) ? in1Len : outPosition + 1;

for (i = start_i; i < end_i; i++) {
   j = outPosition - i;
   *result += in1[i] * in2[j];
}

這樣,條件j >= in2Len永遠不會成立,並且循環測試基本上是測試i < in1Leni < outPosition

從理論上講,你也可以擺脫對j的賦值並將i++轉換為++i ,但編譯器可能已經為你做了那些優化。

  • 您可以在循環之前計算i的正確最小值/最大值,而不是循環中的兩個if語句。

  • 您將分別計算每個結果位置。 相反,您可以將results數組拆分為塊,並讓每個線程計算一個塊。 塊的計算看起來像convolve函數。

除非您的數組非常大,否則使用線程實際上不太可能有用,因為啟動線程的開銷將大於循環的開銷。 但是,讓我們假設您的陣列很大,並且線程是一個凈勝利。 在那種情況下,我會做以下事情:

  • 忘記你當前的convolveHelper ,這太復雜了,也無濟於事。
  • 將循環內部拆分為線程函數。 即只是

     for (j = 0; j < in2Len; j++) { results[i+j] += in1[i] * in2[j]; } 

進入它自己的函數,將i作為參數與其他所有東西一起使用。

  • 有身體convolve只需啟動線程。 為了獲得最大效率,請使用信號量以確保永遠不會創建比核心更多的線程。

答案在於簡單數學而不是多線程(更新)


這就是為什么......

考慮一個b + a c

U可以將其優化為*(b + c) (少一個多重復制)

在你的情況下, 在內循環中存在in2Len不必要的乘法。 哪個可以消除。

因此,如下修改代碼應該給我們reqd卷積:

注意:以下代碼返回循環卷積 ,必須展開循環卷積才能獲得線性卷積結果。

void convolve(const Float64 *in1,
              UInt32 in1Len,
              const Float64 *in2,
              UInt32 in2Len,
              Float64 *results)
{
    UInt32 i, j;

    for (i = 0; i < in1Len; i++) {

        for (j = 0; j < in2Len; j++) {
            results[i+j] += in2[j];
        }

        results[i] = results[i] * in1[i];

    }
}

這應該給U帶來最大的性能跳躍。 試試吧,看看!!

祝好運!!

CVS @ 2600Hertz

我終於想出了如何正確預先計算開始/結束索引( Tyler McHenryinterjay提出的建議):

if (in1Len > in2Len) {
    if (outPosition < in2Len - 1) {
        start = 0;
        end = outPosition + 1;
    } else if (outPosition >= in1Len) {
        start = 1 + outPosition - in2Len;
        end = in1Len;
    } else {
        start = 1 + outPosition - in2Len;
        end = outPosition + 1;
    }
} else {
    if (outPosition < in1Len - 1) {
        start = 0;
        end = outPosition + 1;
    } else if (outPosition >= in2Len) {
        start = 1 + outPosition - in2Len;
        end = in1Len;
    } else {
        start = 0;
        end = in1Len;
    }
}

for (i = start; i < end; i++) {
    *result = in1[i] * in2[outPosition - i];
}

不幸的是,預先計算索引不會導致執行時間明顯減少 :(

讓convolve helper在更大的集合上工作,使用短外循環計算多個結果。

並行化的關鍵是在線程之間的工作分配之間找到一個很好的平衡點。 不要使用比CPU核心數更多的線程。

在所有線程之間平均分配工作。 有了這種問題,每個線程工作的復雜性應該是相同的。

暫無
暫無

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

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