[英]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)。
算法優化幾乎總是優於微優化。
可能有幫助的最明顯的事情是預先計算循環的起始和結束索引,並刪除i
和j
上的額外測試(及其相關的跳轉)。 這個:
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 < in1Len
和i < 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 McHenry和interjay提出的建議):
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.