我有这个函数使用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
答案 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 )) );
}
在你的情况下,无论如何,你有两个问题:
*lhs
和*rhs
代替lhs和rhs;显然,“= m”语法意味着“隐式地使用指向我正在传递给你的东西的指针而不是东西本身。”xmm1
,而不是xmm0
。我已经放了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;
}
}
需要注意的是,数据类型的大小不足以知道您传递的数据类型。仅仅因为模板类型与您正在检查的基本类型共享相同的大小,并不意味着它是相同的类型。所以我强迫演员在我的例子中覆盖这个案例。这通常可能是一种不安全的做法,除非您确定此功能只会与您指定的类型一起使用。例如,使用浮点大小的整数将导致意外错误的答案,编译器无法向您发出警告。