使用MMX内在函数和Microsoft C ++进行堆栈使用

时间:2010-05-23 21:41:58

标签: assembly x86 parallel-processing intrinsics mmx

我有一个内联汇编程序循环,它累积地添加了带有MMX指令的int32数据数组中的元素。特别是,它使用MMX寄存器可容纳16个int32来并行计算16个不同累积和的事实。

我现在想将这段代码转换为MMX内在函数,但我担心会遭受性能损失,因为我不能明确地指示编译器使用8个MMX寄存器来容纳16个独立的总和。

有人可以对此发表评论,也许可以就如何转换下面的代码片段使用内在函数提出解决方案吗?

==内联汇编程序(仅在循环中的一部分)==

paddd   mm0, [esi+edx+8*0]  ; add first & second pair of int32 elements
paddd   mm1, [esi+edx+8*1]  ; add third & fourth pair of int32 elements ...
paddd   mm2, [esi+edx+8*2]
paddd   mm3, [esi+edx+8*3]
paddd   mm4, [esi+edx+8*4]
paddd   mm5, [esi+edx+8*5]
paddd   mm6, [esi+edx+8*6]
paddd   mm7, [esi+edx+8*7]  ; add 15th & 16th pair of int32 elements
  • esi指向数据数组的开头
  • edx为当前循环迭代提供数据数组中的偏移量
  • 数据阵列的排列使得16个独立和的元素是交错的。

1 个答案:

答案 0 :(得分:2)

VS2010使用内在函数对等效代码进行了不错的优化工作。在大多数情况下,它编译内在:

sum = _mm_add_pi32(sum, *(__m64 *) &intArray[i + offset]);

成像:

movq  mm0, mmword ptr [eax+8*offset]
paddd mm1, mm0

这不像你的padd mm1, [esi+edx+8*offset]那么简洁,但它可以说非常接近。执行时间很可能由内存提取占主导地位。

问题在于,VS似乎只想将MMX寄存器添加到其他MMX寄存器中。上述方案仅适用于前7个总和。第8个总和要求将一些寄存器临时保存到存储器中。

这是一个完整的程序及其相应的编译程序集(发布版本):

#include <stdio.h>
#include <stdlib.h>
#include <xmmintrin.h>

void addWithInterleavedIntrinsics(int *interleaved, int count)
{
    // sum up the numbers
    __m64 sum0 = _mm_setzero_si64(), sum1 = _mm_setzero_si64(),
          sum2 = _mm_setzero_si64(), sum3 = _mm_setzero_si64(),
          sum4 = _mm_setzero_si64(), sum5 = _mm_setzero_si64(),
          sum6 = _mm_setzero_si64(), sum7 = _mm_setzero_si64();

    for (int i = 0; i < 16 * count; i += 16) {
        sum0 = _mm_add_pi32(sum0, *(__m64 *) &interleaved[i]);
        sum1 = _mm_add_pi32(sum1, *(__m64 *) &interleaved[i + 2]);
        sum2 = _mm_add_pi32(sum2, *(__m64 *) &interleaved[i + 4]);
        sum3 = _mm_add_pi32(sum3, *(__m64 *) &interleaved[i + 6]);
        sum4 = _mm_add_pi32(sum4, *(__m64 *) &interleaved[i + 8]);
        sum5 = _mm_add_pi32(sum5, *(__m64 *) &interleaved[i + 10]);
        sum6 = _mm_add_pi32(sum6, *(__m64 *) &interleaved[i + 12]);
        sum7 = _mm_add_pi32(sum7, *(__m64 *) &interleaved[i + 14]);
    }

    // reset the MMX/floating-point state
    _mm_empty();

    // write out the sums; we have to do something with the sums so that
    // the optimizer doesn't just decide to avoid computing them.
    printf("%.8x %.8x\n", ((int *) &sum0)[0], ((int *) &sum0)[1]);
    printf("%.8x %.8x\n", ((int *) &sum1)[0], ((int *) &sum1)[1]);
    printf("%.8x %.8x\n", ((int *) &sum2)[0], ((int *) &sum2)[1]);
    printf("%.8x %.8x\n", ((int *) &sum3)[0], ((int *) &sum3)[1]);
    printf("%.8x %.8x\n", ((int *) &sum4)[0], ((int *) &sum4)[1]);
    printf("%.8x %.8x\n", ((int *) &sum5)[0], ((int *) &sum5)[1]);
    printf("%.8x %.8x\n", ((int *) &sum6)[0], ((int *) &sum6)[1]);
    printf("%.8x %.8x\n", ((int *) &sum7)[0], ((int *) &sum7)[1]);
}

void main()
{
    int count        = 10000;
    int *interleaved = new int[16 * count];

    // create some random numbers to add up
    // (note that on VS2010, RAND_MAX is just 32767)
    for (int i = 0; i < 16 * count; ++i) {
        interleaved[i] = rand();
    }

    addWithInterleavedIntrinsics(interleaved, count);
}

这是sum循环内部的生成汇编代码(没有prolog和epilog)。注意大多数总和如何有效保持在mm1-mm6。与mm0对比,mm0用于将数字添加到每个总和,而mm7则用于最后两个总和。这个程序的7和版本似乎没有mm7问题。

012D1070  movq        mm7,mmword ptr [esp+18h]  
012D1075  movq        mm0,mmword ptr [eax-10h]  
012D1079  paddd       mm1,mm0  
012D107C  movq        mm0,mmword ptr [eax-8]  
012D1080  paddd       mm2,mm0  
012D1083  movq        mm0,mmword ptr [eax]  
012D1086  paddd       mm3,mm0  
012D1089  movq        mm0,mmword ptr [eax+8]  
012D108D  paddd       mm4,mm0  
012D1090  movq        mm0,mmword ptr [eax+10h]  
012D1094  paddd       mm5,mm0  
012D1097  movq        mm0,mmword ptr [eax+18h]  
012D109B  paddd       mm6,mm0  
012D109E  movq        mm0,mmword ptr [eax+20h]  
012D10A2  paddd       mm7,mm0  
012D10A5  movq        mmword ptr [esp+18h],mm7  
012D10AA  movq        mm0,mmword ptr [esp+10h]  
012D10AF  movq        mm7,mmword ptr [eax+28h]  
012D10B3  add         eax,40h  
012D10B6  dec         ecx  
012D10B7  paddd       mm0,mm7  
012D10BA  movq        mmword ptr [esp+10h],mm0  
012D10BF  jne         main+70h (12D1070h)  

那你能做什么?

  1. 描述7-sum和8-sum内在程序。选择执行速度更快的那个。

  2. 配置一次只添加一个MMX寄存器的版本。它应该仍然能够利用现代处理器fetch 64 to 128 bytes into the cache at a time这一事实。 8-sum版本比1-sum版本快得多并不明显。 1-sum版本获取完全相同的内存量,并完成相同数量的MMX添加。您需要相应地交错输入。

  3. 如果目标硬件允许,请考虑使用SSE instructions。这些可以一次添加4个32位值。自1999年Pentium III以来,SSE在intel CPU中可用。