SSE(即Streaming SIMD Extension),是對由MMX指令集引進的SIMD模型的擴展。我們知道MMX有兩個明顯的缺點:
- 只能操作整數。
- 不能與浮點數同時運行(MMX使用FPU寄存器作為別名)。
而SSE則解決了這個問題,SSE引進了8個專用的浮點寄存器MMX0~MMX7。后來Intel又陸續推出了SSE2、SSE3、SSE4,這使得SSE指令系列同時擁有了浮點數學運算功能和整數運算功能,因此早先的MMX指令就顯得有點多余了(雖然可是並行執行SSE、MMX指令來提高性能)。
SSE系列功能特點
SSE1
- 添加8個128位寄存器XMM0~MMX7.
- 操作4個單精度浮點數。
- 支持打包(Packed)和標量(Scaler)操作。
SSE2
- 添加整數向量運算,提升運行性能。
- 支持雙精度浮點向量運算。
- 打包類型取消限制,支持8位、16位、32位、64位,包括整數和浮點數。
- 添加緩存控制指令。
- 添加浮點數到整數的轉換指令
SSE3
- 寄存器內水平操作,例如打包在一個128位MMX寄存器內的2個64位整數,可以利用新指令對這兩個整數進行算數運算。
- 多線程優化指令,在超線程下提高性能。
SSSE3
- 打包整形數據的運算加速。
SSE4
SSE4包含兩個子集:SSE4.1和SSE4.2,並兼容以前的64位和IA-32指令集架構。值得指出的是SSE4增加了:1)STTNI(String and Text New Instructions)指令來幫助開發者處理字符搜索和比較,旨在加速對XML文件的解析;2)CRC32指令,幫助計算循環冗余校驗值。
SSE效率對比
這里我們就只簡單比較下這兩個指令集的計算效率,其他功能就不在本次考慮范圍內了。像前一篇博客一樣,我們同樣用對10000000個字符進行加操作來進行對比操作。這里我們全部用Intrinsics來對比,方便編寫,不用考慮調用約定。
整數計算 Mmx vs Sse
因為整數操作只在SSE2中支持,所以實際上我們用的是sse2指令。
mmx代碼:
void calculateUsingMmx(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();
}
sse代碼:
void calculateUsingSseInt(char* data, unsigned size)
{
assert(size % 16 == 0);
__m128i step = _mm_set_epi8(10, 10, 10, 10, 10, 10, 10, 10,
10, 10, 10, 10, 10, 10, 10, 10);
__m128i* dst = reinterpret_cast<__m128i*>(data);
for (unsigned i = 0; i < size; i += 16)
{
auto sum = _mm_add_epi8(step, *dst);
*dst++ = sum;
}
// no need to clear flags like mmx because SSE and FPU can be used at the same time.
}
浮點計算 Asm vs Sse
由於MMX指令集不包含浮點指令,因此我們x86浮點指令來對比,同樣對10000000個flaot值進行加操作。
Asm代碼:
void calculateUsingAsmFloat(float* data, unsigned count)
{
auto singleFloatBytes = sizeof(float);
auto step = 10.0;
__asm
{
push ecx
push edx
mov edx, data
mov ecx, count
fld step // fld only accept FPU or Memory
calcLoop:
fld [edx]
fadd st(0), st(1)
fstp [edx]
add edx, singleFloatBytes
dec ecx
jnz calcLoop
pop edx
pop ecx
}
}
Sse代碼:
void calculateUsingSseFloat(float* data, unsigned count)
{
assert(count % 4 == 0);
assert(sizeof(float) == 4);
__m128 step = _mm_set_ps(10.0, 10.0, 10.0, 10.0);
__m128* dst = reinterpret_cast<__m128*>(data);
for (unsigned i = 0; i < count; i += 4)
{
__m128 sum = _mm_add_ps(step, *dst);
*dst++ = sum;
}
}
運行結果
SSE2的浮點計算對比x86浮點計算性能提升不是非常明顯,但是也要考慮Intrinsics使用導致的略微性能缺失。上面兩種計算方式的效率對比結果如下:
完整代碼見鏈接。