Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/61.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C SSE:使用_mm_add_epi32看不到加速_C_Arrays_X86_Performance Testing_Sse - Fatal编程技术网

C SSE:使用_mm_add_epi32看不到加速

C SSE:使用_mm_add_epi32看不到加速,c,arrays,x86,performance-testing,sse,C,Arrays,X86,Performance Testing,Sse,我希望SSE比不使用SSE更快。我需要添加一些额外的编译器标志吗?是不是因为这是整数代码而不是浮点,所以我没有看到加速 调用/输出 $ make sum2 clang -O3 -msse -msse2 -msse3 -msse4.1 sum2.c ; ./a.out 123 n: 123 SSE Time taken: 0 seconds 124 milliseconds vector+vector:begin int: 1 5 127 0 vector+vector:end int: 0

我希望SSE比不使用SSE更快。我需要添加一些额外的编译器标志吗?是不是因为这是整数代码而不是浮点,所以我没有看到加速

调用/输出

$ make sum2
clang -O3 -msse -msse2 -msse3 -msse4.1 sum2.c ; ./a.out 123
n: 123
  SSE Time taken: 0 seconds 124 milliseconds
vector+vector:begin int: 1 5 127 0
vector+vector:end int: 0 64 66 68
NOSSE Time taken: 0 seconds 115 milliseconds
vector+vector:begin int: 1 5 127 0
vector+vector:end int: 0 64 66 68
编译器

$ clang --version
Apple LLVM version 9.0.0 (clang-900.0.37)
Target: x86_64-apple-darwin16.7.0
Thread model: posix
sum2.c

#include <stdlib.h>
#include <stdio.h>
#include <x86intrin.h>
#include <time.h>
#ifndef __cplusplus
#include <stdalign.h>   // C11 defines _Alignas().  This header defines alignas()
#endif
#define CYCLE_COUNT  10000

// add vector and return resulting value on stack
__attribute__((noinline)) __m128i add_iv(__m128i *a, __m128i *b) {
    return _mm_add_epi32(*a,*b);
}

// add int vectors via sse
__attribute__((noinline)) void add_iv_sse(__m128i *a, __m128i *b, __m128i *out, int N) {
    for(int i=0; i<N/sizeof(int); i++) { 
        //out[i]= _mm_add_epi32(a[i], b[i]); // this also works
        _mm_storeu_si128(&out[i], _mm_add_epi32(a[i], b[i]));
    } 
}

// add int vectors without sse
__attribute__((noinline)) void add_iv_nosse(int *a, int *b, int *out, int N) {
    for(int i=0; i<N; i++) { 
        out[i] = a[i] + b[i];
    } 
}

__attribute__((noinline)) void p128_as_int(__m128i in) {
    alignas(16) uint32_t v[4];
    _mm_store_si128((__m128i*)v, in);
    printf("int: %i %i %i %i\n", v[0], v[1], v[2], v[3]);
}

// print first 4 and last 4 elements of int array
__attribute__((noinline)) void debug_print(int *h) {
    printf("vector+vector:begin ");
    p128_as_int(* (__m128i*) &h[0] );
    printf("vector+vector:end ");
    p128_as_int(* (__m128i*) &h[32764] );
}

int main(int argc, char *argv[]) {
    int n = atoi (argv[1]);
    printf("n: %d\n", n);
    // sum: vector + vector, of equal length
    int f[32768] __attribute__((aligned(16))) = {0,2,4};
    int g[32768] __attribute__((aligned(16))) = {1,3,n};
    int h[32768] __attribute__((aligned(16))); 
    f[32765] = 33; f[32766] = 34; f[32767] = 35;
    g[32765] = 31; g[32766] = 32; g[32767] = 33;

    // https://stackoverflow.com/questions/459691/best-timing-method-in-c
    clock_t start = clock();
        for(int i=0; i<CYCLE_COUNT; ++i) {
            add_iv_sse((__m128i*)f, (__m128i*)g, (__m128i*)h, 32768);
        }
    int msec = (clock()-start) * 1000 / CLOCKS_PER_SEC;
    printf("  SSE Time taken: %d seconds %d milliseconds\n", msec/1000, msec%1000);
    debug_print(h);

    // process intense function again
    start = clock();
        for(int i=0; i<CYCLE_COUNT; ++i) {
            add_iv_nosse(f, g, h, 32768);
        }
    msec = (clock()-start) * 1000 / CLOCKS_PER_SEC;
    printf("NOSSE Time taken: %d seconds %d milliseconds\n", msec/1000, msec%1000);
    debug_print(h);

    return EXIT_SUCCESS;
}
#包括
#包括
#包括
#包括
#ifndef_uucplusplus
#include//C11定义了_Alignas()。此标头定义alignas()
#恩迪夫
#定义循环计数10000
//添加向量并在堆栈上返回结果值
__属性(noinline))\uuuuum128i添加\uiv(\uuuum128i*a,\uuuum128i*b){
返回_mm_add_epi32(*a,*b);
}
//通过sse添加整数向量
__属性(noinline))void add_iv_sse(uu m128i*a、u m128i*b、u m128i*out、int N){

对于(int i=0;i查看asm:clang
-O2
-O3
可能会自动矢量化
add_iv_nosse
(检查重叠,因为您没有使用
int*限制a
等等)

使用
-fno-tree vectorize
禁用自动向量化,而不阻止您使用内部函数。我建议
clang-march=native-mno avx-O3-fno-tree vectorize
测试我认为您要测试的内容,标量整数与传统SSE
padd
。(它在gcc和clang中工作。在clang中,AFAIK是特定于clang的
-fno矢量化的同义词)

顺便说一句,在同一个可执行文件中对这两个文件进行计时会影响到第一个文件,因为CPU不会立即变为全涡轮。在CPU达到全速之前,您可能已经进入了代码的计时部分。(因此,请背对背运行几次,使用
for i in{1..10};do time./a.out;done

在Linux上,我会使用
perf stat-r5./a.out
使用性能计数器运行它5次(我会将它拆分为一个运行测试一个或另一个,这样我可以在整个运行过程中查看perf计数器)


守则检讨:

你忘记了
uint32\u t
stdint.h
。我必须添加它才能将其添加到。(假设clang-5.0与您正在使用的Apple clang版本类似。IDK,如果Apple的clang意味着默认的
-mtune=
选项,但这是有意义的,因为它只针对Mac。此外,基线SSSE3对于x86-64 OS X上的64位也有意义。)

调试打印时不需要
noinline
。此外,我建议为
CYCLE\u COUNT
使用不同的名称。在这种上下文中,Cycles会让我想到时钟周期,所以将其称为
REP\u COUNT
REPEATS
或其他名称

将数组放在
main
中的堆栈上可能没问题。您可以初始化两个输入数组(大部分为零,但添加性能与数据无关)

这很好,因为让它们保持未初始化状态可能意味着每个数组的多个4k页在写入时被复制映射到同一个物理零页,因此您将获得超过预期的L1D缓存命中数

SSE2循环应该是二级/三级缓存带宽的瓶颈,因为工作设置为4*32kiB*3=384kib,所以大约是Intel CPU中256kiB二级缓存的1.5倍

与手动内部循环相比,clang可能更容易展开自动矢量化循环。这可能解释了更好的性能,因为如果每个时钟没有获得2个加载+1个存储,只有16B矢量(不是32B AVX2)可能不会使缓存带宽饱和

更新:实际上循环开销是非常大的,3个指针加上一个循环计数器,并且只展开2个循环来摊销


自动矢量化循环:

.LBB2_12:                               # =>This Inner Loop Header: Depth=1
    movdqu  xmm0, xmmword ptr [r9 - 16]
    movdqu  xmm1, xmmword ptr [r9]         # hoisted load for 2nd unrolled iter
    movdqu  xmm2, xmmword ptr [r10 - 16]
    paddd   xmm2, xmm0
    movdqu  xmm0, xmmword ptr [r10]
    paddd   xmm0, xmm1
    movdqu  xmmword ptr [r11 - 16], xmm2
    movdqu  xmmword ptr [r11], xmm0
    add     r9, 32
    add     r10, 32
    add     r11, 32
    add     rbx, -8               # add / jne  macro-fused on SnB-family CPUs
    jne     .LBB2_12
因此,它是12个融合域UOP,每3个时钟最多可以运行2个向量,限制了前端问题带宽(每时钟4 UOP)

它不使用对齐的加载,因为如果不内联到已知对齐方式的
main
,编译器就没有这些信息,并且您不能保证与
p=\u内置的\u aligned(p,16)
或独立函数中的任何内容对齐。对齐的加载(或AVX)将允许
padd
使用内存操作数,而不是单独的
movdqu
加载

手动矢量化的循环使用对齐的加载来保存前端UOP,但循环计数器的循环开销更大

.LBB1_7:                                # =>This Inner Loop Header: Depth=1
    movdqa  xmm0, xmmword ptr [rcx - 16]
    paddd   xmm0, xmmword ptr [rax - 16]
    movdqu  xmmword ptr [r11 - 16], xmm0

    movdqa  xmm0, xmmword ptr [rcx]
    paddd   xmm0, xmmword ptr [rax]
    movdqu  xmmword ptr [r11], xmm0

    add     r10, 2               # separate loop counter
    add     r11, 32              # 3 pointer incrmeents
    add     rax, 32
    add     rcx, 32
    cmp     r9, r10              # compare the loop counter
    jne     .LBB1_7
所以它是11个融合域UOP。它应该比自动矢量化循环运行得更快。你的计时方法可能导致了这个问题

(除非混合加载和存储实际上会降低优化效果。自动矢量化循环加载了4次,然后存储了2次。事实上,这可以解释这一点。您的数组是4KB的倍数,并且可能所有数组都具有相同的相对对齐方式。因此,您可能会在这里得到4k别名,这意味着CPU不确定存储是否与l重叠oad。我想有一个性能计数器,你可以检查一下。)


另请参见tag wiki中的、和其他链接,特别是《英特尔优化指南》


tag wiki中还有一些很好的SSE/SIMD初学者资料。

看看asm:clang
-O2
-O3
可能会自动矢量化
add_iv_nosse
(检查重叠,因为您没有使用
int*限制a
等等)

使用
-fno-tree vectorize
禁用自动向量化,而不阻止您使用内部函数。我建议
clang-march=native-mno avx-O3-fno-tree vectorize
测试我认为您要测试的内容,标量整数与传统SSE
padd
。(它在gcc和clang中工作。在clang中,AFAIK是特定于clang的
-fno矢量化的同义词)

顺便说一句,在同一个可执行文件中对这两个文件进行计时会影响到第一个文件,因为CPU不会立即变为全涡轮。在CPU达到全速之前,您可能已经进入了代码的计时部分。(因此,请背对背运行几次,使用
for i in{1..10};do time./a.out;done

在Linux上,我会使用
perf stat-r5