【问题标题】:Stack usage with MMX intrinsics and Microsoft C++MMX 内部函数和 Microsoft C++ 的堆栈使用
【发布时间】:2010-05-23 21:41:58
【问题描述】:

我有一个内联汇编程序循环,它使用 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 个独立和的元素相互交错。

【问题讨论】:

    标签: assembly x86 parallel-processing intrinsics mmx


    【解决方案1】:

    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 个总和要求将一些寄存器临时保存到内存中。

    这是一个完整的程序及其对应的编译程序集(release build):

    #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 循环内部生成的汇编代码(没有它的序言和结语)。请注意大多数和如何有效地保存在 mm1-mm6 中。与 mm0 相比,mm0 用于将要添加到每个总和中的数字,而与 mm7 相比,它服务于最后两个总和。这个程序的7-sum版本似乎没有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 以来,英特尔 CPU 就可以使用 SSE。

    【讨论】:

      猜你喜欢
      • 2012-04-21
      • 2019-11-22
      • 1970-01-01
      • 2014-09-06
      • 2015-10-12
      • 1970-01-01
      • 2011-07-27
      • 1970-01-01
      • 2017-01-04
      相关资源
      最近更新 更多