C/C++ 調用avx/sse函數(Intrinsics函數)

原文地址:https://blog.csdn.net/reformatsky/article/details/69388772

最近,實驗室同學要寫一個計算異或校驗的代碼,用在raid6裏,他說kernel裏面用的avx,於是我參考網上一些教程和Intel.org的資料,花了4,5天,踏平了一個大坑之後完成一個簡單的對比測試。IDE 用的qt creator,gcc 需要加 -mavx2
代碼在我的github上avx2_c

網上的博客很多,就不介紹基礎了,講些最重要的。

1. 環境

OS: 本人是 win10 和 centos7,其他的linux應該都差不多
gcc: 低版本的gcc 不支持 -mavx2 參數,具體是在哪個版本加進來的不知道,試試就知道了。
CPU: 其實處理器也需要支持這個指令集,比如avx512需要Intel四代之後纔有,可以去對應廠家的官網上查詢,或者直接跑代碼,看下耗時。

2. immintrin.h

mingw裏面有這個頭文件,包含了各種位數(128bit, 256bit, etc)的類型定義、函數封裝。下面我用到的是256bit的,類型定義在avxintrin.h,函數在avx2intrin.h

3. 關鍵數據結構

3.1 類型

typedef float __m256 __attribute__ ((__vector_size__ (32),
                     __may_alias__));
typedef long long __m256i __attribute__ ((__vector_size__ (32),
                      __may_alias__));
typedef double __m256d __attribute__ ((__vector_size__ (32),
                       __may_alias__));

float, long long, double 指的是構成256bit(32Byte)的unit 類型,add,sub這類算數運算需要注意,and,or,xor這些位運算應該就無所謂了(沒有測試過)。

avxintrin.h裏面,這三個類型的上面其實還有從char 到double的定義,我還沒有使用過,不清楚情況。
下面的代碼都以double爲例,其他幾個類似。

3.2 函數

3.2.1 load

把連續32字節的內存複製到__m256d 類型變量裏

extern __inline __m256d __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm256_load_pd (double const *__P)
{
  return *(__m256d *)__P;
}

extern __inline __m256d __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm256_loadu_pd (double const *__P)
{
  return (__m256d) __builtin_ia32_loadupd256 (__P);
}

兩個函數的區別在: 前者沒有u,後者有u
帶u的表示即使需要複製的內存起始地址不是32的倍數也沒事,應該是彙編那層給處理了,至少不會出錯;
不帶u的,如果起始地址不是32的倍數,運行的時候就會segfault,這個錯誤卡了我三天,根本不知道錯在哪兒,在stackoverflow上搜到一個類似的,外加 Intel org 的視頻,看到 _mm_malloc(申請的內存起始地址是32的倍數) 函數纔想到,網上的博客都沒有提過這個。
帶u和不帶u這個兩個函數是此前 qt creator 的自動補全顯示出來的,當時以爲沒什麼區別,就先用了不帶u的。

3.2.2 運算

extern __inline __m256d __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm256_xor_pd (__m256d __A, __m256d __B)
{
  return (__m256d) __builtin_ia32_xorpd256 ((__v4df)__A, (__v4df)__B);
}

目前我就使用過xor,兩個加數,返回和,沒有碰到什麼坑。

3.2.3 store

這是最後一步,整個流程看起來確實很像彙編。

extern __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm256_store_pd (double *__P, __m256d __A)
{
  *(__m256d *)__P = __A;
}

extern __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm256_storeu_pd (double *__P, __m256d __A)
{
  __builtin_ia32_storeupd256 (__P, (__v4df)__A);
}

和前面load一樣也是內存起始地址是否32倍數的問題,不多說。

4. 啓發

4.1 參考官方資料

在碰到奇怪問題的時候,官方資料的效果不會差。
博客可以快速上手,但是作者不一定踩到所有的坑,碰到這種問題就只能求助於官方資料了。

4.2 自動補全有奇效

如果沒有自動補全,我就得去看頭文件裏面的函數定義,才知道有u和不帶u這兩種函數。

4.3 調試過程要腦洞

在知道是地址問題前,我測試過 。
傳入參數數組or指針。
編譯過程-g -O 。
windows linux環境。
特別是嘗試過程中有次不加-O2就segfault,加了就正常,搞得我一頭霧水。

5 參考

1 http://www.cnblogs.com/zyl910/archive/2012/10/22/simdsumfloat.html
2 http://www.cnblogs.com/zyl910/archive/2012/08/27/intrin_table_gcc.html
3 http://blog.csdn.net/zyl910/article/category/1128358
4 http://stackoverflow.com/questions/4468420/segmentation-fault-due-to-memory-alignment-in-sse
5 intel org 的視頻暫時不記得在哪兒了。。。

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