單指令多數據流,即SIMD(Single Instruction, Multiple Data)指一類能夠在單個指令周期內同時處理多個數據元素的指令集,利用的是數據級並行來提高運行效率,典型的代表由Intel的MMX和SSE指令系列。這類指令的使用環境是對多個數據進行同一種處理,因此典型的應用場景就是多媒體領域,特別是在其中的編解碼流程中。
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指令寄存器。

當使用MMX指令的時候,一個叫做FP(Floating Point) Tag 的Word(2字節)被用來映射浮點寄存器到MMX寄存器。這樣浮點寄存器就成了MMX寄存器的容器,用來執行計算。從浮點指令切換到MMX指令實在處理器內部完成的,不需要人為的操作;相反,從MMX切換到浮點指令時,需要手動調用emms或者__mm_empty()Intrinsics。
MMX指令與x86指令類似可以分為幾類,具體使用及介紹可以參考Oracle的手冊,這里不再重復介紹:
- 數據傳輸指令
- 轉換指令
- 算數指令
- 比較指令
- 邏輯運算指令
- 位移指令
- 狀態管理指令
3. Intrinsics or Asm
我們可以用通常的匯編嵌入方式在C/C++代碼中調用mmx指令,但是這樣一來C/C++開發者可能不是很習慣,尤其是它們沒有接觸過匯編語言的情況下;Intel提供了另一種方式來供開發者選擇----Compiler Intrinsics。
Compiler Intrinsics是內建在編譯器里的函數,Intrinsics通常會以匯編代碼的形式被內聯到代碼中且具有較高的執行效率,因為編譯器知道intrinsics的表現,相比內嵌匯編代碼編譯器能做更多的優化。
同時,Intrinsics的使用方式是停留在宿主語言層的,所以C語言(通常情況下)相比匯編語言擁有的所有優點,Intrinsics都有(比如我可以對Intrinsics數據類型做類型單位的遞增遞減)。
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. 運行結果對比

可以看出運行時間比是 8 : 1.5 : 1左右,完整代碼見鏈接。
