1、算术指令
算术类型函数示例加_mm_add_epi32、_mm256_sub_epi16减_mm_sub_epi32、_mm256_sub_epi16乘_mm_mul_epi32、_mm_mullo_epi32除无水平加/减_mm_hadd_epi16、_mm256_hsub_epi32饱和加/减_mm_adds_epi8、_mm256_subs_epi16最大/最小值_mm_max_epu8、_mm256_min_epi32绝对值_mm_abs_epi16、_mm256_abs_epi32平均值_mm_avg_epu16、_mm256_avg_epu8没有整数除法的SIMD指令。如果要将所有通道都除以一个编译时常数,可以使用一个小技巧:编写一个函数,将相同类型的标量除以该常数,然后使用Compiler Explorer编译成汇编指令,最后移植成相应SIMD指令。例如,要把uint16_t类型的整数除以11,则上述技巧的操作过程如下:- // STEP1: 写一个计算除法的普通函数
- #include <cstdint>
- uint16_t div11(uint16_t a)
- {
- return a / 11;
- }
- // STEP2: 将上面的代码复制到Compiler Explorer中,生成对应的汇编代码如下
- div11(unsigned short):
- push rbp
- mov rbp, rsp
- mov eax, edi
- mov WORD PTR [rbp-4], ax
- movzx eax, WORD PTR [rbp-4]
- movzx eax, ax
- imul eax, eax, 47663
- shr eax, 16
- shr ax, 3
- pop rbp
- ret
- // STEP3: 参考上述汇编代码中的计算方式,编写对应的SIMD指令
- __m128i div_by_11_epu16(__m128i x)
- {
- x = _mm_mulhi_epu16(x, _mm_set1_epi16((short)47663));
- return _mm_srli_epi16(x, 3);
- }
复制代码 整数指令中有一类比较“奇怪”指令,是_mm_sad_epu8(SSE2)和_mm256_sad_epu8(AVX2),它们的运算逻辑相当于以下代码:- array<uint64_t, 4> avx2_sad_epu8(array<uint8_t, 32> a, array<uint8_t, 32> b)
- {
- array<uint64_t, 4> result;
- for (int i = 0; i < 4; i++)
- {
- uint16_t totalAbsDiff = 0;
- for (int j = 0; j < 8; j++)
- {
- const uint8_t va = a[i * 8 + j];
- const uint8_t vb = b[i * 8 + j];
- const int absDiff = abs((int)va - (int)vb);
- totalAbsDiff += (uint16_t)absDiff;
- }
- result[i] = totalAbsDiff;
- }
- return result;
- }
复制代码 它们可能最初是为了视频编码器设计的,用于估算压缩误差。不过这些指令也可以用来做与视频编码无关的事,例如用它们来计算所有字节的总和就非常快速,只要把_mm_sad_epu8第二个参数设为全零向量,然后使用_mm_add_epi64累加结果即可。
2、比较指令
运算符函数示例等于_mm_cmpeq_epi8、_mm256_cmpeq_epi64大于_mm_cmpgt_epi8、_mm256_cmpgt_epi64小于_mm_cmplt_epi8、_mm_cmplt_epi16、_mm_cmplt_epi32整数比较指令只有全通道的版本。与浮点数比较指令类似,整数比较结果也会被设置成全0或者全1。全1的有符号整数等于-1,若要统计比较结果为真的数量,一个技巧是使用下面代码所示的整数减法。使用这个技巧时要注意累加器的整数溢出问题,解决这个问题的一种方法是嵌套循环,内循环保证累加器不会溢出,外循环把内循环的累加结果投射到更宽的整数类型上。- const __m128i cmp = _mm_cmpgt_epi32(val, threshold);
- acc = _mm_sub_epi32(acc, cmp); // acc是保存计数的累加器
复制代码 没有小于等于或大于等于的整数比较指令。如果要比较a |