2013-07-12 61 views
3

有两种方法在使用SSE内在内存寄存器的相互作用:SSE加载/存储内存交易

中级指针:

void f_sse(float *input, float *output, unsigned int n) 
{ 
    _m128 *input_sse = reinterpret_cast<__m128*>(input);//Input intermediate pointer 
    _m128 *output_sse = reinterpret_cast<__m128*>(output);//Output intermediate pointer 
    _m128 s = _mm_set1_ps(0.1f); 
    auto loop_size = n/4; 
    for(auto i=0; i<loop_size; ++i) 
     output_sse[i] = _mm_add_ps(input_sse[i], s); 
} 

显式读取/存储:

void f_sse(float *input, float *output, unsigned int n) 
{ 
    _m128 input_sse, output_sse, result; 
    _m128 s = _mm_set1_ps(0.1f); 
    for(auto i=0; i<n; i+=4) 
    { 
     input_sse = _mm_load_ps(input+i); 
     result  = _mm_add_ps(input_sse, s); 
     _mm_store_ps(output+i, result); 
    } 
} 

什么所提及的方法与哪种方法在性能方面更好?输入和输出指针由_mm_malloc()对齐。

+0

如果第一个例子的赋值操作使用未对齐的指令,它将会变慢。 _mm_store_ps对齐存储不是吗?第一个例子与元素复制类似。你可以显示反汇编输出? –

回答

1

编译时的g ++优化级别O3(使用objdump -d)内循环的汇编代码是

20: 0f 28 04 07    movaps (%rdi,%rax,1),%xmm0 
24: 0f 58 c1    addps %xmm1,%xmm0 
27: 0f 29 04 06    movaps %xmm0,(%rsi,%rax,1) 
2b: 48 83 c0 10    add $0x10,%rax 
2f: 48 39 d0    cmp %rdx,%rax 
32: 75 ec     jne 20 <_Z5f_ssePfS_j+0x20> 

10: 0f 28 04 07    movaps (%rdi,%rax,1),%xmm0 
14: 83 c1 04    add $0x4,%ecx 
17: 0f 58 c1    addps %xmm1,%xmm0 
1a: 0f 29 04 06    movaps %xmm0,(%rsi,%rax,1) 
1e: 48 83 c0 10    add $0x10,%rax 
22: 39 ca     cmp %ecx,%edx 
24: 77 ea     ja  10 <_Z5f_ssePfS_j+0x10> 

他们是非常相似的。在第一个g ++中设法只使用一个计数器(只有一个add指令)。所以我想它更好。

1

我使用g ++ -O2编译了两个样本,我发现的主要区别是edx(n)中的值的使用方式不同,导致代码略有不同。

第一功能:

0000000000000000 <_Z6f_sse2PfS_j>: 
    0: c1 ea 02    shr $0x2,%edx  # loop_size = n/4. 
    3: 85 d2     test %edx,%edx 
    5: 74 2d     je  34 <_Z6f_sse2PfS_j+0x34> 
    7: 83 ea 01    sub $0x1,%edx 
    a: 0f 28 0d 00 00 00 00 movaps 0x0(%rip),%xmm1  # 11 <_Z6f_sse2PfS_j+0x11> 
    11: 48 83 c2 01    add $0x1,%rdx 
    15: 31 c0     xor %eax,%eax 
    17: 48 c1 e2 04    shl $0x4,%rdx    // Adjust for loop size vs. index. 
    1b: 0f 1f 44 00 00   nopl 0x0(%rax,%rax,1) 
    20: 0f 28 04 07    movaps (%rdi,%rax,1),%xmm0 
    24: 0f 58 c1    addps %xmm1,%xmm0 
    27: 0f 29 04 06    movaps %xmm0,(%rsi,%rax,1) 
    2b: 48 83 c0 10    add $0x10,%rax 
    2f: 48 39 d0    cmp %rdx,%rax 
    32: 75 ec     jne 20 <_Z6f_sse2PfS_j+0x20> 
    34: f3 c3     repz retq 

二级功能:

0000000000000000 <_Z5f_ssePfS_j>: 
    0: 85 d2     test %edx,%edx 
    2: 74 22     je  26 <_Z5f_ssePfS_j+0x26> 
    4: 0f 28 0d 00 00 00 00 movaps 0x0(%rip),%xmm1  # b <_Z5f_ssePfS_j+0xb> 
    b: 31 c0     xor %eax,%eax 
    d: 31 c9     xor %ecx,%ecx 
    f: 90      nop 
    10: 0f 28 04 07    movaps (%rdi,%rax,1),%xmm0 
    14: 83 c1 04    add $0x4,%ecx 
    17: 0f 58 c1    addps %xmm1,%xmm0 
    1a: 0f 29 04 06    movaps %xmm0,(%rsi,%rax,1) 
    1e: 48 83 c0 10    add $0x10,%rax 
    22: 39 ca     cmp %ecx,%edx 
    24: 77 ea     ja  10 <_Z5f_ssePfS_j+0x10> 
    26: f3 c3     repz retq 

我又看了看生成的代码,以及与此想出了:

void f_sse2(float *input, float *output, unsigned int n) 
{ 
    __m128 *end = reinterpret_cast<__m128*>(&input[n]); 
    __m128 *input_sse = reinterpret_cast<__m128*>(input);//Input intermediate pointer 
    __m128 *output_sse = reinterpret_cast<__m128*>(output);//Output intermediate pointer 
    __m128 s = _mm_set1_ps(0.1f); 
    while(input_sse < end) 
     *output_sse++ = _mm_add_ps(*input_sse++, s); 
} 

产生这样的代码:

0000000000000000 <_Z6f_sse2PfS_j>: 
    0: 89 d2     mov %edx,%edx 
    2: 48 8d 04 97    lea (%rdi,%rdx,4),%rax 
    6: 48 39 c7    cmp %rax,%rdi 
    9: 73 23     jae 2e <_Z6f_sse2PfS_j+0x2e> 
    b: 0f 28 0d 00 00 00 00 movaps 0x0(%rip),%xmm1  # 12 <_Z6f_sse2PfS_j+0x12> 
    12: 66 0f 1f 44 00 00  nopw 0x0(%rax,%rax,1) 
    18: 0f 28 07    movaps (%rdi),%xmm0 
    1b: 48 83 c7 10    add $0x10,%rdi 
    1f: 0f 58 c1    addps %xmm1,%xmm0 
    22: 0f 29 06    movaps %xmm0,(%rsi) 
    25: 48 83 c6 10    add $0x10,%rsi 
    29: 48 39 f8    cmp %rdi,%rax 
    2c: 77 ea     ja  18 <_Z6f_sse2PfS_j+0x18> 
    2e: f3 c3     repz retq 

我认为这可能更有效一点,但可能不值得改变它。但它给了我15分钟的时间。