AVX图像算法优化系列二: 使用AVX2指令集加速查表算法。( 二 )


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这个数据 。
那么注意看这些gather函数,最下的操作单位都是int32,因此,如果我们的查找表是byte或者short类型,这个就有点困难了,正如我们上面的Cure函数一样,是无法直接使用这个函数的 。
【AVX图像算法优化系列二: 使用AVX2指令集加速查表算法。】那么我我们来看看一个正常的int型表,使用两者之间大概有什么区别呢,以及是如何使用该函数的,为了测试公平,我把正常的查找表也做了展开 。
int main(){const int Length = 4000 * 4000;int *Src = https://www.huyubaike.com/biancheng/(int *)calloc(Length, sizeof(int));int *Dest = (int *)calloc(Length, sizeof(int));int *Table = (int *)calloc(65536, sizeof(int));for (int Y = 0; Y < Length; Y++)Src[Y] = rand();//产生的随机数在0-65535之间,正好符号前面表的大小for (int Y = 0; Y < 65536; Y++){Table[Y] = 65535 - Y;//随意的分配一些数据}LARGE_INTEGER nFreq;//LARGE_INTEGER在64位系统中是LONGLONG,在32位系统中是高低两个32位的LONG,在windows.h中通过预编译宏作定义LARGE_INTEGER nBeginTime;//记录开始时的计数器的值LARGE_INTEGER nEndTime;//记录停止时的计数器的值double time;QueryPerformanceFrequency(&nFreq);//获取系统时钟频率QueryPerformanceCounter(&nBeginTime);//获取开始时刻计数值for (int Y = 0; Y < Length; Y += 4){Dest[Y + 0] = Table[Src[Y + 0]];Dest[Y + 1] = Table[Src[Y + 1]];Dest[Y + 2] = Table[Src[Y + 2]];Dest[Y + 3] = Table[Src[Y + 3]];}QueryPerformanceCounter(&nEndTime);//获取停止时刻计数值time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) * 1000 / (double)nFreq.QuadPart;//(开始-停止)/频率即为秒数,精确到小数点后6位printf("%f\n", time);QueryPerformanceCounter(&nBeginTime);//获取开始时刻计数值for (int Y = 0; Y < Length; Y += 16){__m256i Index0 = _mm256_loadu_si256((__m256i *)(Src + Y));__m256i Index1 = _mm256_loadu_si256((__m256i *)(Src + Y + 8));__m256i Value0 = _mm256_i32gather_epi32(Table, Index0, 4);__m256i Value1 = _mm256_i32gather_epi32(Table, Index1, 4);_mm256_storeu_si256((__m256i *)(Dest + Y), Value0);_mm256_storeu_si256((__m256i *)(Dest + Y + 8), Value1);}QueryPerformanceCounter(&nEndTime);//获取停止时刻计数值time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) * 1000 / (double)nFreq.QuadPart;//(开始-停止)/频率即为秒数,精确到小数点后6位printf("%f\n", time);free(Src);free(Dest);free(Table);getchar();return 0;}直接使用这句即可完成查表工作:__m256i Value0 = _mm256_i32gather_epi32(Table, Index0, 4);
这是一个比较简单的应用场景,在我本机的测试中,普通C语言的耗时大概是27ms,AVX版本的算法那耗时大概是17ms,速度有1/3的提升 。考虑到加载内存和保存数据在本代码中占用的比重明显较大,因此,提速还是相当明显的 。

经验总结扩展阅读