AVX圖像算法優化系列二: 使用AVX2指令集加速查表算法。

查表算法,無疑也是一種非常常用、有效而且快捷的算法,我們在很多算法的加速過程中都能看到他的影子 , 在圖像處理中 , 尤其常用,比如我們常見的各種基于直方圖的增強,可以說,在photoshop中的調整菜單里80%的算法都是用的查表,因為他最終就是用的曲線調整 。
普通的查表就是提前建立一個表,然后在執行過程中算法計算出一個索引值,從表中查詢索引對應的表值,并賦值給目標地址,比如我們常用的曲線算法如下所示:
int IM_Curve_PureC(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, unsigned char *TableB, unsigned char *TableG, unsigned char *TableR){int Channel = Stride / Width;if (Channel == 1){for (int Y = 0; Y < Height; Y++){unsigned char *LinePS = Src + Y * Stride;unsigned char *LinePD = Dest + Y * Stride;for (int X = 0; X < Width; X++){LinePD[X] = TableB[LinePS[X]];}}}else if (Channel == 3){for (int Y = 0; Y < Height; Y++){unsigned char *LinePS = Src + Y * Stride;unsigned char *LinePD = Dest + Y * Stride;for (int X = 0; X < Width; X++){LinePD[0] = TableB[LinePS[0]];LinePD[1] = TableG[LinePS[1]];LinePD[2] = TableR[LinePS[2]];LinePS += 3;LinePD += 3;}}}return IM_STATUS_OK;}通常我們認為這樣的算法是很高效的,當然,我們其實還可以做一定的優化,比如使用下面的四路并行:
int IM_Curve_PureC(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, unsigned char *TableB, unsigned char *TableG, unsigned char *TableR){int Channel = Stride / Width;if ((Channel != 1) && (Channel != 3))return IM_STATUS_INVALIDPARAMETER;if ((Src =https://www.huyubaike.com/biancheng/= NULL) || (Dest == NULL))return IM_STATUS_NULLREFRENCE;if ((Width <= 0) || (Height <= 0))return IM_STATUS_INVALIDPARAMETER;int BlockSize = 4, Block = Width / BlockSize;if (Channel == 1){for (int Y = 0; Y < Height; Y++){unsigned char *LinePS = Src + Y * Stride;unsigned char *LinePD = Dest + Y * Stride;for (int X = 0; X < Block * BlockSize; X += BlockSize){LinePD[X + 0] = TableB[LinePS[X + 0]];LinePD[X + 1] = TableB[LinePS[X + 1]];LinePD[X + 2] = TableB[LinePS[X + 2]];LinePD[X + 3] = TableB[LinePS[X + 3]];}for (int X = Block * BlockSize; X < Width; X++){LinePD[X] = TableB[LinePS[X]];}}}else if (Channel == 3){for (int Y = 0; Y < Height; Y++){unsigned char *LinePS = Src + Y * Stride;unsigned char *LinePD = Dest + Y * Stride;for (int X = 0; X < Block * BlockSize; X += BlockSize){LinePD[0] = TableB[LinePS[0]];LinePD[1] = TableG[LinePS[1]];LinePD[2] = TableR[LinePS[2]];LinePD[3] = TableB[LinePS[3]];LinePD[4] = TableG[LinePS[4]];LinePD[5] = TableR[LinePS[5]];LinePD[6] = TableB[LinePS[6]];LinePD[7] = TableG[LinePS[7]];LinePD[8] = TableR[LinePS[8]];LinePD[9] = TableB[LinePS[9]];LinePD[10] = TableG[LinePS[10]];LinePD[11] = TableR[LinePS[11]];LinePS += 12;LinePD += 12;}for (int X = Block * BlockSize; X < Width; X++){LinePD[0] = TableB[LinePS[0]];LinePD[1] = TableG[LinePS[1]];LinePD[2] = TableR[LinePS[2]];LinePS += 3;LinePD += 3;}}}return IM_STATUS_OK;}這樣效率能進一步的提高 。
在早期我們的關注中,我也一直想再次提高這個算法的效率 , 但是一直因為他太簡單了,而無法有進一步的提高,在使用SSE指令集時,我們也沒有找到合適的指令,只有當查找表為16字節的表時,可以使用_mm_shuffle_epi8快速實現,詳見【算法隨記七】巧用SIMD指令實現急速的字節流按位反轉算法 。 一文的描述 。
在我們再次接觸AVX指令集,正如上一篇關于AVX指令的文章所述 , 他增加了非常具有特色的gather系列指令,具體有哪些如下圖所示:

AVX圖像算法優化系列二: 使用AVX2指令集加速查表算法。

文章插圖
有一大堆啊,其實看明白了,就只有2大類,每大類里有2個小系列 , 每個系列里又有4中數據類型,
兩大類為 :針對128位的類型的gather和針對256位的gather 。
【AVX圖像算法優化系列二: 使用AVX2指令集加速查表算法。】兩個系列為:帶mask和不帶mask系列 。
4中數據類型為: int32、int64、float、double 。
當然,里面還有一些64為地址和32位地址的區別 , 因此又增加了一些列的東西 , 我個人認為其中最常用的函數只有4個,分別是:_mm_i32gather_epi32 、_mm256_i32gather_epi32、_mm_i32gather_ps、_mm256_i32gather_ps,我們以_mm256_i32gather_epi32為例 。
注意,這里所以下,不要以為_mm_i32gather_ps這樣的intrinsics指令以_mm開頭,他就是屬于SSE的指令 , 實際行他并不是,他是屬于AVX2的 , 只是高級別的指令集對老指令的有效補充 。
_mm256_i32gather_epi32的相關說明如下:
AVX圖像算法優化系列二: 使用AVX2指令集加速查表算法。

文章插圖
其作用 , 翻譯過來就是從固定的基地址base_addr開始,燃用偏移量由 vindex提供 , 注意這里的vindex是一個__m256i數據類型,里面的數據要把它看成8個int32類型,即保存了8個數據的地址偏移量,最后一個scale表示地址偏移量的放大系數 , 容許的值只有1、2、4、8,代表了字節,雙字節,四字節和把字節的意思 , 通常_mm256_i32gather_epi32一般都是使用的4這個數據 。

推薦閱讀