SSE2指令不适用于使用C ++的内联汇编

时间:2010-11-16 01:38:11

标签: c++ gcc inline-assembly sse2

我有这个函数使用SSE2将一些值加在一起它应该将lhs和rhs加在一起并将结果存储回lhs:

template<typename T>
void simdAdd(T *lhs,T *rhs)
{
    asm volatile("movups %0,%%xmm0"::"m"(lhs));
    asm volatile("movups %0,%%xmm1"::"m"(rhs));

    switch(sizeof(T))
    {
        case sizeof(uint8_t):
        asm volatile("paddb %%xmm0,%%xmm1":);
        break;

        case sizeof(uint16_t):
        asm volatile("paddw %%xmm0,%%xmm1":);
        break;

        case sizeof(float):
        asm volatile("addps %%xmm0,%%xmm1":);
        break;

        case sizeof(double):
        asm volatile("addpd %%xmm0,%%xmm1":);
        break;

        default:
        std::cout<<"error"<<std::endl;
        break;
    }

    asm volatile("movups %%xmm0,%0":"=m"(lhs));
}

我的代码使用如下函数:

float *values=new float[4];
float *values2=new float[4];

values[0]=1.0f;
values[1]=2.0f;
values[2]=3.0f;
values[3]=4.0f;

values2[0]=1.0f;
values2[1]=2.0f;
values2[2]=3.0f;
values2[3]=4.0f;

simdAdd(values,values2);
for(uint32_t count=0;count<4;count++) std::cout<<values[count]<<std::endl;

然而这不起作用,因为当代码运行时,输出1,2,3,4而不是2,4,6,8

2 个答案:

答案 0 :(得分:5)

我发现内联汇编支持在大多数现代编译器中都不可靠(例如,实现只是简单的错误)。通常最好使用compiler intrinsics,这些声明看起来像C函数,但实际上编译为特定的操作码。

Intrinsics允许您指定操作码的确切序列,但将寄存器着色留给编译器。它比在C变量和asm寄存器之间移动数据更可靠,这是内联汇编程序总是让我失望的地方。它还允许编译器安排您的指令,如果它适用于pipeline hazards,则可以提供更好的性能。即,在这种情况下你可以做

void simdAdd(float *lhs,float *rhs)
{
   _mm_storeu_ps( lhs, _mm_add_ps(_mm_loadu_ps( lhs ), _mm_loadu_ps( rhs )) );
}

在你的情况下,无论如何,你有两个问题:

  1. 糟糕的GCC内联汇编语法,它使得指针和值之间的差异大大混淆。使用*lhs*rhs代替lhs和rhs;显然,“= m”语法意味着“隐式地使用指向我正在传递给你的东西的指针而不是东西本身。”
  2. GCC具有源语言,目标语法 - addps将其结果存储在第二个参数中,因此您需要输出xmm1,而不是xmm0
  3. 我已经放了a fixed example on codepad(以避免弄乱这个答案,并证明它有效)。

答案 1 :(得分:0)

我认为这里的事情错了。首先,加载XMM寄存器并将值存储回变量的语句是错误的。

asm volatile("movups %0,%%xmm0"::"m"(lhs));
asm volatile("movups %0,%%xmm1"::"m"(rhs));
...
asm volatile("movups %%xmm0,%0":"=m"(lhs));

应该阅读

asm volatile("movups %0,%%xmm0"::"m"(*lhs));
asm volatile("movups %0,%%xmm1"::"m"(*rhs));
...
asm volatile("movups %%xmm0,%0":"=m"(*lhs));

请注意*。你正在加载并添加指针值,然后将它们存储回一个用于传递指针参数的临时值(因此在函数调用返回时不会写入内存时会忘记它。)

即使有这些修复,一般来说,这不是一个好方法。我用asm语句编写了我自己的例子,但它有缺陷,因为我忘了考虑传入的参数的未对齐性。使用asm语句变得非常麻烦,使用内部函数更容易和更易读。请谨慎使用正确的数据类型:

template<typename T>
void simdAdd(T *lhs,T *rhs)
{
    switch(sizeof(T))
    {
        case sizeof(uint8_t):
        {
          __m128i lh128;
          lh128 = _mm_add_epi8( _mm_loadu_si128( (__m128i *)lhs ),
                                _mm_loadu_si128( (__m128i *)rhs ) );
          _mm_storeu_si128( (__m128i *)lhs, lh128 );
        }
        break;

        case sizeof(uint16_t):
        {
          __m128i lh128;
          lh128 = _mm_add_epi16( _mm_loadu_si128( (__m128i *)lhs ),
                                 _mm_loadu_si128( (__m128i *)rhs ) );
          _mm_storeu_si128( (__m128i *)lhs, lh128 );
        }
        break;

        case sizeof(float):
        {
          __m128 lh128;
          lh128 = _mm_add_ps( _mm_loadu_ps( (float *)lhs ),
                              _mm_loadu_ps( (float *)rhs ) );
          _mm_storeu_ps( (float *)lhs, lh128 );
        }
        break;

        case sizeof(double):
        {
          __m128d lh128;
          lh128 = _mm_add_pd( _mm_loadu_pd( (double *)lhs ),
                              _mm_loadu_pd( (double *)rhs ) );
          _mm_storeu_pd( (double *)lhs, lh128 );
        }
        break;

        default:
        std::cout<<"error"<<std::endl;
        break;
    }
}

需要注意的是,数据类型的大小不足以知道您传递的数据类型。仅仅因为模板类型与您正在检查的基本类型共享相同的大小,并不意味着它是相同的类型。所以我强迫演员在我的例子中覆盖这个案例。这通常可能是一种不安全的做法,除非您确定此功能只会与您指定的类型一起使用。例如,使用浮点大小的整数将导致意外错误的答案,编译器无法向您发出警告。