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 。
两个系列为:带mask和不带mask系列 。

经验总结扩展阅读