簡體   English   中英

SSE2指令不適用於C ++的內聯匯編

[英]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 )) );
}

無論如何,您有兩個問題:

  1. 糟糕的GCC內聯匯編語法使指針和值之間的差異非常混亂。 使用*lhs*rhs而不是lhs和rhs; 顯然,“ = m”語法的意思是“隱式使用指向我要傳遞給您的東西的指針,而不是它本身。”
  2. GCC具有source,destination語法-addps將其結果存儲在第二個參數中,因此您需要輸出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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM