x86平台SIMD编程入门(4):整型指令
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 <= b这样情况,可以使用min(a, b) == a这样的方法来实现。
没有无符号整数的比较指令。如果有需要,可以参考下面的方法手动实现:
__m128i cmpgt_epu16(__m128i a, __m128i b)
{
const __m128i highBit = _mm_set1_epi16((short)0x8000);
a = _mm_xor_si128(a, highBit);
b = _mm_xor_si128(b, highBit);
return _mm_cmpgt_epi16(a, b);
}
movemask指令只有8位整数的版本。如果想要在通用寄存器中获得32位整数的比较结果,一种变通的方法是先把__m128i重解释转换成__m128然后使用_mm_movemask_ps(对于64位结果则是先转换成__m128d然后使用_mm_movemask_pd)。
3、移位指令
3.1、寄存器移位
| 函数示例 | 说明 |
|---|---|
_mm_slli_si128 |
对__m128i寄存器整体进行左移 |
_mm_srli_si128 |
对__m128i寄存器整体进行右移 |
_mm256_slli_si256 |
对__m256i寄存器中的高低两个128位数据分别进行左移(如果要对256位数据整体移位,可以参考这个stackoverflow链接) |
_mm256_srli_si256 |
对__m256i寄存器中的高低两个128位数据分别进行右移 |
_mm_alignr_pi8 |
将两个__m64输入向量首尾拼接后右移 |
_mm_alignr_epi8 |
将两个__m128i输入向量首尾拼接后右移 |
_mm256_alignr_epi8 |
将两个__m256i输入向量中的高低128位分别首尾拼接后右移 |
上表中的最小移位步长都是1字节。
3.2、通道移位
下表所列的函数是对每个通道都做等长的移位操作。例如_mm_srli_epi16(x, 4)会把通道中的0x8015转换为0x0801。
| 函数示例 | 说明 |
|---|---|
_mm_slli_epi16、_mm_slli_epi32、_mm_slli_epi64 |
对__m128i寄存器的每个通道都做等长的左移 |
_mm_srli_epi16、_mm_srli_epi32、_mm_srli_epi64 |
对__m128i寄存器的每个通道都做等长的右移 |
_mm256_slli_epi16、_mm256_slli_epi32、_mm256_slli_epi64 |
对__m256i寄存器的每个通道都做等长的左移 |
_mm256_srli_epi16、_mm256_srli_epi32、_mm256_srli_epi64 |
对__m256i寄存器的每个通道都做等长的右移 |
还有一类移位函数会保留符号位,它们是_mm_srai_epi16、_mm_srai_epi32、_mm256_srai_epi16、_mm256_srai_epi32。这类函数可能是为了弥补整数除法指令的缺失,例如_mm_srai_epi16(x, 4)会把通道中的0x8015转换为0xF801,它相当于为有符号的int16_t整数做了除法x / 16。
AVX2引入了一系列指令来为每个通道分别指定不同的移位长度,它们是_mm_sllv_epi32、_mm_sllv_epi64、_mm_srlv_epi32、_mm_srlv_epi64以及对应的_mm256前缀版本。
4、打包与解包指令
| 函数示例 | 说明 |
|---|---|
_mm_unpacklo_epi32 |
输入两个向量[a, b, c, d]和[e, f, g, h],返回[a, e, b, f]。 |
_mm_unpackhi_epi32 |
输入两个向量[a, b, c, d]和[e, f, g, h],返回[c, g, d, h] |
_mm_packs_epi16 |
输入两个有符号整数向量,使用饱和运算将每个通道打包为位宽减半的类型 |
_mm_packus_epi16 |
输入两个无符号整数向量,使用饱和运算将每个通道打包为位宽减半的类型 |
unpacklo/unpackhi指令的一种用法是:如果第二个输入向量为全0,就可以把无符号整数转换到更宽的类型,例如8位无符号整数变为16位无符号整数。不过,也有指令可以直接实现无符号整数向更宽类型的转换,例如_mm_cvtepu16_epi32、_mm256_cvtepu8_epi32等。
5、洗牌指令
| 函数示例 | 说明 | 示意图 |
|---|---|---|
_mm_shuffle_epi32 |
右图中,控制常数是0x0D(二进制 00 00 11 01)。输出向量的4个通道分别来自输入向量的0b01、0b11、0b00、0b00号通道。 | ![]() |
_mm_shufflelo_epi16 |
对低4个通道进行洗牌,高4个通道直接复制。右图中的控制常数是0x0D。 | ![]() |
_mm_shufflehi_epi16 |
对高4个通道进行洗牌,低4个通道直接复制。右图中的控制常数是0x0D。 | ![]() |
_mm_insert_epi16 |
插入一个整数。与浮点数插入指令不同的是,插入的整数来自通用寄存器。 | ![]() |
_mm_blend_epi16 |
混合两个寄存器的通道。右图中的控制常数是0xB8(二进制 10111000)。 | ![]() |
_mm_broadcastb_epi8_mm_broadcastw_epi16_mm_broadcastd_epi32_mm_broadcastq_epi64 |
把最低的通道广播到其它通道,右图是_mm_broadcastd_epi32。 |
![]() |
_mm_blendv_epi8 |
与blend指令不同的是,混合位掩码不直接编码到指令中,而是使用另一个寄存器。 | |
_mm256_permutevar8x32_epi32 |
接收一个包含源数据的整数寄存器和一个包含源索引的整数寄存器,根据索引值选择通道。 | |
_mm_shuffle_epi8 |
与其它类型的shuffle指令不同,这是唯一一条运行时按变量洗牌的指令。 |
x86平台SIMD编程入门(4):整型指令的更多相关文章
- x86平台转x64平台关于内联汇编不再支持的解决
x86平台转x64平台关于内联汇编不再支持的解决 2011/08/25 把自己碰到的问题以及解决方法给记录下来,留着备用! 工具:VS2005 编译器:cl.exe(X86 C/C+ ...
- C/C++的64位整型
在C/C++中,64为整型一直是一种没有确定规范的数据类型.现今主流的编译器中,对64为整型的支持也是标准不一,形态各异.一般来说,64位整型的定义方式有long long和__int64两种(VC还 ...
- 编译器是如何实现32位整型的常量整数除法优化的?[C/C++]
引子 在我之前的一篇文章[ ThoughtWorks代码挑战——FizzBuzzWhizz游戏 通用高速版(C/C++ & C#) ]里曾经提到过编译器在处理除数为常数的除法时,是有优化的,今 ...
- C++64位整型
今天在Ubuntu下编译C++代码,然后毫无防备的出现以下错误: 查阅了相关资料,__int64是VC++独有的,因此64位g++无法识别. 以下内容转载自:Byvoid 在C/C++中,64位整型一 ...
- Python入门篇-基础数据类型之整型(int),字符串(str),字节(bytes),列表(list)和切片(slice)
Python入门篇-基础数据类型之整型(int),字符串(str),字节(bytes),列表(list)和切片(slice) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Py ...
- 微信公众平台中临时二维码的scene_id为32位非0整型
原文:微信公众平台中临时二维码的scene_id为32位非0整型 微信公众平台中临时二维码的scene_id为32位非0整 ...
- 【C语言入门教程】2.3 整型数据
没有小数位或指数的数据类型被称为整型数据,根据使用方法的分类,整型数据可分为整型常量和整型变量.根据定义或显示的数制分类,可分为十进制.八进制和十六进制. 2.3.1 整型常量 整型常量是在运算中不可 ...
- Linux漏洞分析入门笔记-CVE_2018_6323_整型溢出
操作系统 Ubuntu 16.04 /32 位 调试器 IDA pro 7.0 漏洞软件 binutils-2.29.1 0x00: 漏洞描述 1.什么是整数溢出: 在计算机中,整数分 ...
- python基础入门 整型 bool 字符串
整型,bool值,字符串 一.整型 整型十进制和二进制 整型:整型在Python中的关键字用int来表示; 整型在计算机中是用于计算和比较的 可进行+ - * / % //(整除) **(幂运算) 十 ...
- 从入门到自闭之Python整型,字符串以及for循环
Day 01 整型: 对比: 在python 2 版本中有整型,长整型long 在python 3 版本中全部都是整型 用于计算和比较 整型和布尔值的转换 二进制转换成十进制: print (in ...
随机推荐
- UE4纯C++实现游戏快捷栏之将快捷栏注册到玩家状态
我们有了UI有了物品信息,接下来我们便需要给每一个玩家绑定一个快捷栏了,我们分以下几部分来注册我们玩家的快捷栏. 1.Types.h:定于ShortcutContainer类,定义快捷栏的单个容器结构 ...
- vim粘贴文件格式不乱
vim粘贴防止格式乱,配置以下命令然后在粘贴,即可~ :set paste
- 数据结构课程设计报告-C 语言整数单链表的表示和实现
数据结构课程设计报告 专业名称:计算机科学与技术 课程名称:数据结构 实训题目:整数单链表的表示和实现 实训环境:C 语言实现( D ...
- delphi Image32 SVG图形查看器
DELPHI 中没有SVG显示组件,需要用到第三方组件,高版本可以使用skia(但必须带上skia.dll). 最新版Image32修改了很多,SVGIconImageList 也因此换成了Image ...
- OSG开发笔记(三十三):同时观察物体不同角度的多视图从相机技术
前言 前面的相机hud可以单独显示图形,继续深入研究相机hud,技术就是子视图了,实现该功能的直接技术是从相机技术. 本篇描述osg从相机技术 Demo 相机视口的关键调用 ...
- nemu-wsl-环境配置
实在是不愿意用学校的虚拟平台,觉得在自己的电脑上留存一部分真的很有意思,也想捣鼓一下,于是在自己电脑上配置下最基本的环境,做下记录 准备好wsl 因为要求环境是 Ubuntu 18.04 和 gcc- ...
- Java 并发编程实战学习笔记——CountDownLatch的使用
public class CountDownLatch extends Object 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 用给定的计数 初始化 Co ...
- 《刚刚问世》系列初窥篇-Java+Playwright自动化测试-6- 元素基础定位方式-上篇 (详细教程)
1.简介 从这篇文章开始,就开始要介绍UI自动化核心的内容,也是最困难的部分了,就是:定位元素,并去对定位到的元素进行一系列相关的操作.想要对元素进行操作,第一步,也是最重要的一步,就是要找到这个元素 ...
- Redis集群搭建-多服务器
条件 l 基于redis5.0.*版本进行集群的搭建 l 集群要求至少六个节点,即每个主节点配置1个从节点,本集群是使用了3个主节点并每个下面挂一个从节点,三个主节点分布在3台服务器上 l 集群配置信 ...
- Threejs的三维坐标系
在三维空间中,所有的物体和相机都需要基于一个统一的坐标系来进行定位和操作.理解坐标系的基本概念,对于创建稳定.准确的三维效果至关重要. 基础 Three.js 采用的是右手坐标系,这意味着如果你将右手 ...





