加速大量与数组相关的计算,visual studio

时间:2016-04-15 16:24:08

标签: c++ c visual-studio parallel-processing simd

我想知道加速大量数组计算的最佳方法是什么。让我们说我有这种情况:

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实例都将处理单独的文件。

什么能让我获得最大的收益?谢谢你的任何建议!

2 个答案:

答案 0 :(得分:2)

首先,对类型使用_t名称,而不是数组。让我们调用数组template_multipliers[]

如果template_multipliersconst且变量为unsigned,编译器可以在编译时对其进行求和,并完全优化内循环。

对于gcc,我们也可以通过提升template_t的总和来获得更好的代码。在这种情况下,它设法在编译时与int而不是unsigned int进行求和。

有符号溢出是未定义的行为,这可能就是为什么gcc有时候不知道它将在其优化的目标机器上实际发生什么。 (例如,在x86上,它不是你需要避免的东西。一系列添加的最终结果并不取决于操作的顺序,即使某些订单在临时结果中产生签名溢出而某些订单也是#39; t。gcc并不总是在签名案例中利用附加的关联性。)

这纯粹是一个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 auto-vectorizes this的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个融合域uop,因为Haswell及更高版本pmulld为2 uops(即使使用单寄存器寻址模式也无法进行微融合) 。这意味着循环每3个时钟只能运行一次迭代。通过使用目标的指针增量和grc的dst + src-dst 2寄存器寻址模式,gcc可以节省2个uop(因此它将在每2个时钟一次迭代运行)(因为它可以&#39;无论如何都是微熔丝。

请参阅godbolt Compiler Explorer link,了解OP代码修改较少版本的完整来源,该代码并未提升template_multipliers的总和。这让人觉得很奇怪:

    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是非常量的。

答案 1 :(得分:0)

编译器已经为您完成的一个简单优化是:

df <- df[, c(3,4,2)]

stylized_rearranger <- function(df) {
and just do the above steps within and return
df
}

进一步查看此模板并将模板定义为1,2,3,... 125,然后 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 * 4 .. * 125,这是常数(让我们称之为p2),所以:

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]; } } 平行。