我们回到刚才的关于Curve函数的应用,因为gather相关指令最小的收集粒度都是32位,因此,对于字节版本的表是无论为力的,但是为了能借用这个函数实现查表,我们可以稍微对输入的参数做些手续,再次构造一个int类型的表格,即使用如下代码(弧度版本,Channel == 1):
int Table[256];for (int Y = 0; Y < 256; Y++){Table[Y] = TableB[Y];}这样这个表就可以用了,对于24位我们也可以用类似的方式构架一个256*3个int元素的表 。
但是我们又面临着另外一个问题,即_mm256_i32gather_epi32这个返回的是8个int32类型的整形数,而我们需要的返回值确实字节数,所以这里就又涉及到8个int32数据转换为8个字节数并保存的问题,当然为了更为高效的利用指令集,我们这里考虑同时把2个__m256i类型里的16个int32数据同时转换为16个字节数,这个可以用如下的代码高效的实现:
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){__m128i SrcV = _mm_loadu_si128((__m128i *)(LinePS + X));//int32A0A1A2A3A4A5A6A7__m256i ValueL = _mm256_i32gather_epi32(Table, _mm256_cvtepu8_epi32(SrcV), 4);//int32B0B1B2B3B4B5B6B7__m256i ValueH = _mm256_i32gather_epi32(Table, _mm256_cvtepu8_epi32(_mm_srli_si128(SrcV, 8)), 4);//shortA0A1A2A3B0B1B2B3A4A5A6A7B4B5B6B7__m256i Value = https://www.huyubaike.com/biancheng/_mm256_packs_epi32(ValueL, ValueH);//byteA0A1A2A3B0B1B2B300000000A4A5A6A7B4B5B6B700000000Value = _mm256_packus_epi16(Value, _mm256_setzero_si256());//byteA0A1A2A3A4A5A6A7B0B1B2B3B4B5B6B70000000000000000Value = _mm256_permutevar8x32_epi32(Value, _mm256_setr_epi32(0, 4, 1, 5, 2, 3, 6, 7));_mm_storeu_si128((__m128i *)(LinePD + X), _mm256_castsi256_si128(Value));}for (int X = Block * BlockSize; X < Width; X++){LinePD[X] = TableB[LinePS[X]];} 上面的代码里涉及到了没有按常规方式出牌的_mm256_packs_epi32、_mm256_packus_epi16等等,最后我们也是需要借助于AVX2提供的_mm256_permutevar8x32_epi32才能把那些数据正确的调整为需要的格式 。
对于彩色的图像,就要稍微复杂一些了,因为涉及到RGB格式的排布,同时考虑一些对齐问题,最友好的方式就是一次性处理8个像素,24个字节,这一部分留给有兴趣的读者自行研究 。
在我本机的CPU中测试呢,灰度版本的查找表大概有20%的提速,彩色版本的要稍微多一些,大概有30%左右 。
这些提速其实不太明显,因为在整个过程中处理内存耗时较多,他并不是以计算为主要过程的算法,当我们某个算法中见也有查找时,并且为了计算查找表时,需要很多的数学运算去进行隐射的坐标计算时,这个时候这些隐射计算通常都是有浮点参与,或其他各种复杂的计算参与,这个时候用SIMD指令计算这些过程是能起到很大的加速作用的,在我们没有AVX2之前,使用SSE实现时,到了进行查表时通常的做法都是把前通过SSE计算得到的坐标的_m128i元素的每个值使用_mm_extract_epi32(这个是内在的SSE指令,不是用其他伪指令拼合的)提取出每个坐标值,然后在使用_mm_set相关的函数把查找表的返回值拼接成一个行的SSE变量,以便进行后续的计算,比如下面的代码:
文章插图
这个时候使用AVX2的这个指令就方便了,如下所示:
文章插图
注意到上面的Texture其实是个字节类型的数组,也就是一副图像,对应的C代码如下所示:
int SampleXF = IM_ClampI(ClipXF >> 16, 0, Width - 1);//试着拆分VX和VY的符号情况分开写,减少ClampI的次数,结果似乎区别不是特别大,因此优化意义不大int SampleXB = IM_ClampI(ClipXB >> 16, 0, Width - 1);int SampleYF = IM_ClampI(ClipYF >> 16, 0, Height - 1);int SampleYB = IM_ClampI(ClipYB >> 16, 0, Height - 1);unsigned char *SampleF = Texture + (SampleYF * Stride + SampleXF);unsigned char *SampleB = Texture + (SampleYB * Stride + SampleXB);Sum += SampleF[0] + SampleB[0];
经验总结扩展阅读
- BLS签名算法
- 从源码分析 MGR 的新主选举算法
- Upscayl,免费开源的 AI 图像增强软件
- GC plan_phase二叉树挂接的一个算法
- AVX图像算法优化系列一: 初步接触AVX。
- 含源码 手把手教你使用LabVIEW OpenCV dnn实现图像分类
- 什么是a3算法
- 根号加根号怎么算
- 独辟蹊径:逆推Krpano切图算法,实现在浏览器切多层级瓦片图
- 数列的四种表示方法