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这个数据 。

推荐阅读