【彙編優化】之x86架構intrinsic優化

1、SSE Intrisic based on x86

x86架構下的優化有多種手段,常見的有純彙編優化、inline彙編、Intrinsic優化。前兩種對編譯器的依賴比較大,跨平臺(windows\linux等)編譯問題比較多,例如純彙編:win32、win64、linux64下函數形參入棧規則都不一樣,且需要保護的寄存器也有較大區別,此外還有彙編重定位問題等等;而inline彙編在windows下是intel格式,在linux下是AT&T格式。intrinsic入門快,且沒有以上諸多問題,比較適合初學者,但是其性能相對純彙編或Inline彙編,會差一些。本文就intrinsic優化作一些簡單的介紹,具體需結合實踐理解學習。

1.1 指令集對應的位數

指令集類型 所佔位數
– MMX : Multi Media Extensions 8 x 64bit (1997)
– SSE/SSE2/SSE3/SSSE3/SSE4.x : Streaming SIMD Extensions 8 x 128bit (1999)
– AVX/AVX2/FMA : Advanced Vector Extensions 16 x 256 bit (2008)
– AVX-512/KNC : Advanced Vector Extensions 32 x 512 bit (2012)

備註:第二列8*64代表MMX寄存器有8個,每個是64位;8x128道理類似。

1.2 指令集intrinsic版對應的頭文件

(1)、不同版本的指令集對應的intrinsic頭文件如表1.1所示;表1.1中也列出了不同版本的VS對Intrinsic的支持能力不一。此外,需要注意的是1>、在VS中如果想win32及win64都能編譯通過,應避免使用__m64類型的數據,數據類型在本文中會有講述;2>、如果想在linux中編譯成功,需在編譯選項中加 -msse、-msse4.1等。
                        表1.1 指令集對應的頭文件

File 描述 VS VisualStudio
intrin.h AllArchitectures 8.0 2005
mmintrin.h MMXintrinsics 6.0 6.0SP5+PP5
xmmintrin.h StreamingSIMDExtensionsintrinsics 6.0 6.0SP5+PP5
emmintrin.h WillametteNewInstructionintrinsics(SSE2) 6.0 6.0SP5+PP5
pmmintrin.h SSE3intrinsics 9.0 2008
tmmintrin.h SSSE3intrinsics 9.0 2008
smmintrin.h SSE4.1intrinsics 9.0 2008
nmmintrin.h SSE4.2intrinsics. 9.0 2008
wmmintrin.h AESandPCLMULQDQintrinsics. 10.0 2010
immintrin.h Intel-specificintrinsics(AVX) 10.0 2010SP1

2、數據類型

Data type 表現形式
__m64 eight 8-bit values, four 16-bit values, two 32-bit values, or one 64-bit value.
__m128 = {float f0, f1, f2, f3}
__m128d = {double d0, d1}
__m128i 16 8-bit, 8 16-bit, 4 32-bit, or 2 64-bit ints
__m265 256-bit as eight single-precision floating-point values
__m265d 256-bit as four double-precision floating-point values
__m256i 256-bit as integers, (bytes, words, etc.)

備註:MMX指令集使用 64位數據類型、SSE指令集使用 128數據類型、AVX指令集使用256位數據類型,如圖2.1所示:
圖2.1 數據類型及其可佔位數

2.1 數據類型的轉換

SSE彙編指令和其Intrinsic函數之間基本存在這一一對應的關係,有了彙編的實現再改爲Intrinsic是挺簡單的,再在這羅列代碼也乜嘢什麼意義了。這裏就記錄下使用的過程中遇到的最大的問題:數據類型之間的轉換。 做圖像處理,由於像素通道值是8位的無符號整數,而與其運算的往往又是浮點數,這就需要將8位無符號整數轉換爲浮點數;運算完畢後,得到的結果又要寫回圖像通道,就要是8位無符號整數,還要涉及到超出8位的截斷。開始不注意時吃了大虧….
  類型轉換主要以下幾種:
    1>. 浮點數和整數的轉換及32位浮點數和64位浮點數之間的轉換。 這種轉換簡單直接,只需要調用相應的函數指令即可。
    2>. 有符號整數的高位擴展將8位、16位、32位有符號整數擴展爲16位、32位、64位。
    3>. 有符號整數的截斷 將16位、32位、64位有符號壓縮
    4>. 無符號整數到有符號整數的擴展
  在Intrinsic函數中 上述類型轉換的格式
     _mm_cvtepixx_epixx (xx是位數8/16/32/64)這是有符號整數之間的轉換
     _mm_cvtepixx_ps / _mm_cvtepixx_pd 整數到單精度/雙精度浮點數之間的轉換
     _mm_cvtepuxx_epixx 無符號整數向有符號整數的擴展,採用高位0擴展的方式,這些函數是對無符號高位0擴展變成相應位數的有符號整數。沒有32位無符號整數轉換爲16位有符號整數這樣的操作。
    _mm_cvtepuxx_ps / _mm_cvtepuxx_pd 無符號整數轉換爲單精度/雙精度浮點數。

上面的數據轉換還少了一種,整數的飽和轉換。什麼是飽和轉換呢,超過的最大值的以最大值來計算,例如8位無符號整數最大值爲255,則轉換爲8位無符號時超過255的值視爲255。
  整數的飽和轉換有兩種:
   1>、有符號之間的 SSE的Intrinsic函數提供了兩種

__m128i _mm_packs_epi32(__m128i a, __m128i b)
__m128i _mm_packs_epi16(__m128i a , __m128i b)

用於將16/32位的有符號整數飽和轉換爲8/16位有符號整數。
  2>、有符號到無符號之間的 SSE的Intrinsic函數提供了兩種

__m128i _mm_packus_epi32(__m128i a, __m128i b)
__m128i _mm_packus_epi16(__m128i a , __m128i b)

用於將16/32位的有符號整數飽和轉換爲8/16位無符號整數

3、 intrinsic函數命名

Naming convention: _mm_<intrin_op>_<suffix>                               

where mm is the prefix for working on the 64-bit registers or 128-bit registers; intrin_op is the operation, like add for addition or sub for subtraction; and suffix denotes the type of data to operate on, with the first letters denoting packed §, extended packed (ep), or scalar (s). The remaining letters are the types given in the table below.suffix Markings

suffix 數據類型
[s/d] Single- or double-precision floating point
[i/u]nnn Signed or unsigned integer of bit size nnn, where nnn is 128, 64, 32, 16, or 8
[ps/pd/sd] Packed single, packed double, or scalar double
epi32 Extended packed 32-bit signed integer
si256 Scalar 256-bit integer

4、示例

c代碼

void add(float *a, float *b, float *c)
{
   int i;
   for (i = 0; i < 4; i++) {
       c[i] = a[i] + b[i];
    }
}

對應的Intrinsic代碼

#include <xmmintrin.h>
void add(float *a, float *b, float *c)
{
    __m128 t0, t1;
    t0 = _mm_load_ps(a);
    t1 = _mm_load_ps(b);
    t0 = _mm_add_ps(t0, t1);
    _mm_store_ps(c, t0);
}

備註:代碼摘自https://software.intel.com/sites/default/files/managed/9e/bc/64-ia-32-architectures-optimization-manual.pdf 4.3.1.2 小節

5、附錄

intel intrinsic手冊 : https://software.intel.com/en-us/articles/intel-sdm

5.1 Latency and Throughput

Latency: 指令佔據多少個時鐘週期,此後數據纔可被下一條指令使用。
Throughput:指令佔據運算單元多少個時鐘週期,此後運算單元纔可被下一條指令使用。
參考網址:https://software.intel.com/en-us/articles/measuring-instruction-latency-and-throughput

參考網址:
https://blog.csdn.net/brookicv/article/details/52295043
https://software.intel.com/en-us/comment/1758892
http://verchetensna.ga/software/4247intel-intrinsics-guide-download.html#
https://www-m17.ma.tum.de/foswiki/pub/M17/Lehrstuhl/LehreWiSe1516ATHPSC/INT1.pdf
https://db.in.tum.de/~finis/x86-intrin-cheatsheet-v2.1.pdf
https://www.inf.ethz.ch/personal/markusp/teaching/263-2300-ETH-spring11/slides/class17.pdf
http://sseplus.sourceforge.net/fntable.html
https://stackoverflow.com/questions/661338/sse-sse2-and-sse3-for-gnu-c/662250#662250
https://stackoverflow.com/questions/7156908/sse-intrinsic-functions-reference
https://gcc.gnu.org/onlinedocs/gcc-4.3.3/gcc/i386-and-x86_002d64-Options.html#i386-and-x86_002d64-Options
http://www.agner.org/optimize/
http://www.ehu.eus/sgi/ARCHIVOS/c_ug_lnx.pdf

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章