[英]Speeding up large amounts of array related computation, visual studio
我想知道加速大量數組計算的最佳方法是什么。 可以說我有這種情況:
int template_t[] = {1, 2, 3, 4, 5, 6, ...., 125};
int image[3200][5600];
int template_image[3200][5600];
for(int i = 0; i < 3200; i++) {
for(int j = 0; j < 5600; j++) {
// iterate over template to find template value per pixel
for(int h = 0; h < template_length; h++)
template_image[i][j] += template_t[h] * image[i][j];
}
}
當然,我的情況要復雜得多,但是同樣的想法也適用。 我有一個表示圖像中像素的大數組,我需要對每個像素應用一些模板數組,以計算要放置在模板圖像中的值。
我已經考慮過幾種加快處理速度的方法:
什么會給我最大的收益呢? 感謝您的任何建議!
首先,對類型而不是數組使用_t
名稱。 我們將其稱為數組template_multipliers[]
。
如果template_multipliers
是const
,並且變量是unsigned
,則編譯器可以在編譯時對其求和,並完全優化內部循環。
對於gcc,通過將template_t
的總和提升到循環之外,我們還可以獲得更好的代碼。 在這種情況下,即使在使用int
而不是unsigned int
情況下,它也設法在編譯時求和。
有符號的溢出是不確定的行為,這可能就是為什么gcc有時不知道要對其進行優化的目標計算機上實際發生什么的原因。 (例如,在x86上,您不必避免這種情況。一系列加法的最終結果並不取決於運算的順序,即使某些指令在臨時結果中產生帶符號的溢出而有些則不是。gcc不會t在簽名的情況下始終利用加法的關聯性)。
這純粹是gcc的限制。 您的代碼必須避免按源級別的操作順序進行有符號的溢出,但是如果編譯器知道通過執行其他更快的操作將獲得相同的結果,則可以並且應該這樣做。
// aligning the arrays makes gcc's asm output *MUCH* shorter: no fully-unrolled prologue/epilogue for handling unaligned elements
#define DIM1 320
#define DIM2 1000
alignas(32) unsigned int image[DIM1][DIM2];
alignas(32) unsigned int template_image[DIM1][DIM2];
// with const, gcc can sum them at compile time.
const
static unsigned int template_multipliers[] = {1, 2, 3, 4, 5, 6, 7, 8, 8, 10, 11, 12, 13, 125};
const static int template_length = sizeof(template_multipliers) / sizeof(template_multipliers[0]);
void loop_hoisted(void) {
for(int i = 0; i < DIM1; i++) {
for(int j = 0; j < DIM2; j++) {
// iterate over template to find template value per pixel
unsigned int tmp = 0;
for(int h = 0; h < template_length; h++)
tmp += template_multipliers[h];
template_image[i][j] += tmp * image[i][j];
}
}
}
具有-O3 -fverbose-asm -march=haswell
gcc 5.3使用以下內部循環自動將其矢量化 :
# gcc inner loop: ymm1 = set1(215) = sum of template_multipliers
.L2:
vpmulld ymm0, ymm1, YMMWORD PTR [rcx+rax] # vect__16.10, tmp115, MEM[base: vectp_image.8_4, index: ivtmp.18_90, offset: 0B]
vpaddd ymm0, ymm0, YMMWORD PTR [rdx+rax] # vect__17.12, vect__16.10, MEM[base: vectp_template_image.5_84, index: ivtmp.18_90, offset: 0B]
vmovdqa YMMWORD PTR [rdx+rax], ymm0 # MEM[base: vectp_template_image.5_84, index: ivtmp.18_90, offset: 0B], vect__17.12
add rax, 32 # ivtmp.18,
cmp rax, 4000 # ivtmp.18,
jne .L2 #,
在Intel Haswell的內部循環中,這是9個融合域pmulld
,因為pmulld
在Haswell及更高版本上是2 pmulld
(即使是單寄存器尋址模式也無法微熔絲)。 這意味着該循環每3個時鍾只能運行一次迭代。 通過使用指向目標的指針增量和為dst + src-dst
使用dst + src-dst
2寄存器尋址模式,gcc可以節省2微碼(因此它將每2個時鍾運行一次迭代),因為src無法微處理-保險絲)。
請參閱Godbolt編譯器資源管理器鏈接 ,以獲取OP代碼的未修改版本的完整源代碼,該版本不會增加template_multipliers的總和。 它使怪異的asm:
unsigned int tmp = template_image[i][j];
for(int h = 0; h < template_length; h++)
tmp += template_multipliers[h] * image[i][j];
template_image[i][j] = tmp;
.L8: # ymm4 is a vector of set1(198)
vmovdqa ymm2, YMMWORD PTR [rcx+rax] # vect__22.42, MEM[base: vectp_image.41_73, index: ivtmp.56_108, offset: 0B]
vpaddd ymm1, ymm2, YMMWORD PTR [rdx+rax] # vect__1.47, vect__22.42, MEM[base: vectp_template_image.38_94, index: ivtmp.56_108, offset: 0B]
vpmulld ymm0, ymm2, ymm4 # vect__114.43, vect__22.42, tmp110
vpslld ymm3, ymm2, 3 # vect__72.45, vect__22.42,
vpaddd ymm0, ymm1, ymm0 # vect__2.48, vect__1.47, vect__114.43
vpaddd ymm0, ymm0, ymm3 # vect__29.49, vect__2.48, vect__72.45
vpaddd ymm0, ymm0, ymm3 # vect_tmp_115.50, vect__29.49, vect__72.45
vmovdqa YMMWORD PTR [rdx+rax], ymm0 # MEM[base: vectp_template_image.38_94, index: ivtmp.56_108, offset: 0B], vect_tmp_115.50
add rax, 32 # ivtmp.56,
cmp rax, 4000 # ivtmp.56,
jne .L8 #,
每次循環時,它都會對template_multipliers
進行一些求和。 循環中的添加數量根據數組中的值(而不僅僅是值的數量)而變化。
這些優化中的大多數應適用於MSVC,除非整個程序的鏈接時優化允許它進行求和,即使template_multipliers
是非常量。
一個簡單的優化,應該是編譯器不應該為您做的:
int p = template_image[i][j], p2= image[i][j];
// iterate over template to find template value per pixel
for(int h = 0; h < template_length; h++)
p += template_t[h] * p2;
template[i][j]= p;
再看一遍,並將模板的定義定義為1、2、3,.. 125,然后將p2
乘以1 * 2 * 3 * 4 .. * 125,它是常數(我們稱之為CT
),所以:
for (h..
template_image[i][j] += template_t[h] * image[i][j];
相當於
template_image[i][j] += CT * image[i][j];
因此該算法變為:
#define CT 1*2*3*4*5*6*7...*125 // must stil lbe completed
int image[3200][5600];
int template_image[3200][5600];
for(int i = 0; i < 3200; i++) {
for(int j = 0; j < 5600; j++) {
template_image[i][j] += CT * image[i][j];
}
}
這可以在j
上並行化。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.