簡體   English   中英

加速與數組相關的大量計算,Visual Studio

[英]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];

    }
}

當然,我的情況要復雜得多,但是同樣的想法也適用。 我有一個表示圖像中像素的大數組,我需要對每個像素應用一些模板數組,以計算要放置在模板圖像中的值。

我已經考慮過幾種加快處理速度的方法:

  • SIMD指令? 但是我似乎找不到任何資源來在Visual Studio中編寫SIMD特定代碼。
  • 並行化-盡管我已經對整個執行本身進行了並行化,所以該程序基於X內核運行自身的X個實例。 該程序的輸入是大量的圖像文件,因此那些X實例都將處理單獨的文件。

什么會給我最大的收益呢? 感謝您的任何建議!

首先,對類型而不是數組使用_t名稱。 我們將其稱為數組template_multipliers[]

如果template_multipliersconst ,並且變量是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.

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