[英]SSE2 instructions not working in inline assembly with C++
我有一個使用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
我發現在大多數現代編譯器中,內聯程序集支持都不可靠(例如,實現只是普通的錯誤)。 通常,最好使用編譯器內部函數 ,它們是類似於C函數的聲明,但實際上是編譯為特定的操作碼。
內部函數可讓您指定操作碼的確切順序,但將寄存器的顏色留給編譯器。 這比嘗試在C變量和asm寄存器之間移動數據要可靠得多,這是內聯匯編程序總是對我不利的地方。 它還可以讓編譯器安排您的指令,如果它可以解決管道危險 ,則可以提供更好的性能。 也就是說,在這種情況下,您可以
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
。 幾件事我在這里看錯了。 首先,加載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;
}
}
要知道的是,數據類型的大小不足以知道您傳遞了哪種數據類型。 模板類型與您要檢查的基本類型具有相同的大小,並不意味着它是同一類型。 因此,在我的示例中,我強制轉換覆蓋這種情況。 這通常是不安全的做法,除非您確定此功能僅會與指定的類型一起使用。 例如,使用浮點大小的整數將導致意外的錯誤答案,並且編譯器將無法對其發出警告。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.