SIMD---MMX代碼優化

單指令多數據流,即SIMD(Single Instruction, Multiple Data)指一類可以在單個指令週期內同時處理多個數據元素的指令集,利用的是數據級並行來提升運行效率,典型的表明由Intel的MMX和SSE指令系列。這類指令的使用環境是對多個數據進行同一種處理,所以典型的應用場景就是多媒體領域,特別是在其中的編解碼流程中。html

1. SIMD優缺點

1.1 優勢

  • 效率高:單指令多數據流意味着只須要一個指令週期就能同時對多個數據進行批處理,雖然該類指令自己的指令週期可能會較通常的指令長,可是總體考慮確定是提升了處理效率。

1.2 缺點

  • 適用場景有限:並非全部的狀況都能使用SIMD,有些狀況下就算能使用,也須要對原有算法進行不小的改動。
  • 增大功耗和芯片面積:由於多數據流,cpu須要更大的寄存器來存儲這些數據。
  • 人爲編寫:目前編譯器對SIMD的翻譯有限,使用時須要開發者人爲編寫。
  • 固定的數據元素個數:例如MMX指令,只能對1個64位、2個32位、4個16位、8個8位數據進行批量處理,其餘位長的數據元素須要特殊處理。好比對6個8位元素進行處理,須要額外填充剩餘的2個字節。

2. MMX指令簡介

MMX指令有8個64位寄存器(MM0~MM7),但MMX實際上並無硬件支持的新寄存器,它使用浮點寄存器來模擬MMX指令寄存器。git

mmx-pic

當使用MMX指令的時候,一個叫作FP(Floating Point) Tag 的Word(2字節)被用來映射浮點寄存器到MMX寄存器。這樣浮點寄存器就成了MMX寄存器的容器,用來執行計算。從浮點指令切換到MMX指令實在處理器內部完成的,不須要人爲的操做;相反,從MMX切換到浮點指令時,須要手動調用emms或者**__mm_empty()**Intrinsics。github

MMX指令與x86指令相似能夠分爲幾類,具體使用及介紹能夠參考Oracle的手冊,這裏再也不重複介紹:算法

  • 數據傳輸指令
  • 轉換指令
  • 算數指令
  • 比較指令
  • 邏輯運算指令
  • 位移指令
  • 狀態管理指令

3. Intrinsics or Asm

咱們能夠用一般的彙編嵌入方式在C/C++代碼中調用mmx指令,可是這樣一來C/C++開發者可能不是很習慣,尤爲是它們沒有接觸過彙編語言的狀況下;Intel提供了另外一種方式來供開發者選擇----Compiler Intrinsics。oracle

Compiler Intrinsics是內建在編譯器裏的函數,Intrinsics一般會以彙編代碼的形式被內聯到代碼中且具備較高的執行效率,由於編譯器知道intrinsics的表現,相比內嵌彙編代碼編譯器能作更多的優化函數

同時,Intrinsics的使用方式是停留在宿主語言層的,因此C語言(一般狀況下)相比彙編語言擁有的全部優勢,Intrinsics都有(好比我能夠對Intrinsics數據類型作類型單位的遞增遞減)。oop

4. 效率比較

咱們這裏分別簡單測試C++、Intrinsics(使用MMX)、Asm(使用MMX)三種形式代碼的執行效率,示例中咱們分別對內存中的100 000 000個字節進行算數運算:測試

4.1 C++代碼

void calculateUsingCpp(char* data, unsigned size)
{
    assert(size % 8 == 0);

    unsigned step = 10;
    for (unsigned i = 0; i < size; ++i)
    {
        *data++ += step;
    }
}

4.2 Intrinsics代碼

Intrinsics代碼中,咱們每次執行mmx Intrinsics時都打包8個字節的數據並執行加操做,執行完mmx指令後咱們須要調用**_mm_empty()** Intrinsics來取消mmx指令對浮點寄存器的別名映射:優化

void calculateUsingIntrinsics(char* data, unsigned size)
{
    assert(size % 8 == 0);

    __m64 step = _mm_set_pi8(10, 10, 10, 10, 10, 10, 10, 10);
    __m64* dst = reinterpret_cast<__m64*>(data);
    for (unsigned i = 0; i < size; i += 8)
    {
        auto sum = _mm_adds_pi8(step, *dst);
        *dst++ = sum;
    }

    _mm_empty();
}

4.3 Asm代碼

Intel彙編語法在嵌入到高級語言代碼中時能夠直接使用上下文中的變量,這一點很是方便:翻譯

void calculateUsingAsm(char* data, unsigned size)
{
    assert(size % 8 == 0);

    unsigned loopCount = size / 8;
    __int64 value = 0x0a0a0a0a0a0a0a0a;
    __asm 
    {
            push eax
            push ecx

            mov eax, data
            mov ecx, loopCount
            movq mm1, value

        startLoop:
            movq mm0, [eax]
            paddb  mm0, mm1
            movq [eax], mm0

            add eax, 8
            dec ecx
            jnz startLoop

            emms
            pop ecx
            pop eax
    }
}

5. 運行結果對比

result-pic

能夠看出運行時間比是 8 : 1.5 : 1左右,完整代碼見連接

相關文章
相關標籤/搜索