首页 > 其他分享 >Intel指令集及SIMD数据加速

Intel指令集及SIMD数据加速

时间:2023-07-27 20:55:35浏览次数:50  
标签:mm256 ps pd Intel __ 指令集 SIMD 128 向量

查看CPU相关信息

image
执行结果举例:
image

查看电脑CPU支持的指令集:

cat /proc/cpuinfo | grep "processor" | wc -l

支持的指令集:
image

向量指令集

Flynn分类法根据指令数据进入CPU的方式,将计算机 架构分为四种不同的类型。

  • 1.单指令流单数据流(SISD,Single Instruction stream Single Data stream)
  • 2.单指令流多数据流(SIMD,Single Instruction stream Multiple Data stream)
  • 3.多指令流单数据流(MISD,Multiple Instruction stream Single Data stream)
  • 4.多指令流多数据流(MIMD,Multiple Instruction stream Multiple Data stream)

举例:
SISD指令:需要多条指令完成相关操作
image

SIMD指令:只需要单条指令就可完成相关操作
image

API 介绍

官方参考文档见: Intel® Intrinsics Guide
需要什么功能,可对应该文档查看对应的API
image

因API太多,整个左侧区域是用来筛选,左侧上方的选择区域使用来按指令集来筛选,左测下方的选择区域是用来按功能筛选。

数据类型

可简单理解为,MMX对应64位,SSE对应128位,AVX对应256位
intel 指令集的变量命名规则位__mm<bit_width><data_type>,其中bit_width即数据长度(64|128|256),data_type即存储的数据是什么类型(i|d),i对应int,d对应double,要是空,则对应float

数据类型 描述 大小
__m64 包含2个单精度(float)浮点数的64位向量 2 x 32 bit
__m128 包含4个单精度浮点数的128位向量 4 x 32 bit
__m128i 包含数个整型数值的128位向量 128 bit
__m128d 包含2个双精度浮点数的128位向量 2 x 64 bit
__m256 包含8个单精度浮点数的256位向量 8 x 32 bit
__m256i 包含数个整型数值的256位向量 256 bit
__m256d 包含4个双精度浮点数的256位向量 4 x 256 bit

SSE Data Type(16 XMM Registers)
image

AVX Data Types(16 YMM Registers)
image

注:
AVX的256位寄存器和SSE的128位寄存器存在着相互重叠的关系(XMM寄存器位YMM寄存器的低位),所以最好不要混用AVX与SSE指令集,否则会导致transition penalty(过度处罚),慢50~80时钟周期
image

函数类型:
SSE/AVX intrinsic functions的命名格式位_mm<bit_width>_<name>_<data_type>

  • <bit_width>:表明向量的位长度,对于128位的向量,这个参数为空;256位的向量,这个参数位256
  • <name>:描述内联函数的算术操作
  • <data_type>:标识函数主参数的数据类型。
  • ps 包含float 类型的向量,把32 bits当作一个数
  • pd包含double类型的向量,把64bits当作一个数
  • epi8/epi16/epi32/epi64 :向量中每个数都是整型,包含8位/16位/32位/64位的有符号整数
  • epu8/epu16/epu32/epu64: 向量中每个数都是无符号整型(unsigned),包含8位/16位/32位/64位的无符号整数
  • m128/m128i/m128d/m256/m256i/m256d:输入值与返回类型不同时,标识输入向量类型
    例如:__m256i _mm256_setr_m128i(__m128i lo,__m128i hi) 输入两个__m128i向量,把他们拼在一起,变成一个__m256i返回,另外这种结尾只见于load
  • si128/si256:未指定的128或者256位向量,不关心向量里到底是什么类型,反正是128bit/256bit
    例如:__m256i _mm_broadcastsi128_si256 (__m128i a)
函数 描述
load 用于从内存加载到寄存器
set 用于从内存加载到寄存器
add、sub、mul、div 加减乘除
store 用于从寄存器写入到内存
cast 数据类型转换
compare 数据比较
logical(and|or|test) 逻辑运算
mask 条件判断

一个小demo

#include <iostream>
#include <immintrin.h>
int main(int argc, char* agv[])
{
    __m256 a = _mm256_set_ps(8.0, 7.0, 6.0,5.0,
                            4.0, 3.0, 2.0, 1.0);
    __m256 b = _mm256_set_ps(18.0, 17.0, 16.0, 15.0,
                            14.0, 13.0, 12.0, 11.0);
    __m256 c = _mm256_add_ps(a, b);

    float d[8];
    _mm256_storeu_ps(d, c);

    std::cout << "result equals:" << d[0] <<","<<d[1]<<","<<d[2]<<","
                <<d[4]<<","<<d[5]<<","<<d[6]<<","<<d[7]<<std::endl;
    return 0;
}

编译时,需要增加编译参数-msse -mavx -mavx2

g++ simple_demo.cc -mavx2 -mavx -msse

运行结果:
result equals:12,14,16,20,22,24,26

PS:

编译参数 -mavx 是用于启用 AVX(Advanced Vector Extensions)指令集的选项。
AVX 是 Intel 推出的一种 SIMD(Single Instruction, Multiple Data)扩展指令集,它可以提高向量化计算的性能。
在使用 g++ 编译器时,可以使用 -mavx 参数来启用 AVX 指令集的支持
需要注意的是,使用 -mavx 参数编译的程序只能在支持 AVX 指令集的 CPU 上运行,否则可能会导致程序崩溃或产生错误。因此,在使用 -mavx 参数之前,建议先检查目标机器是否支持 AVX 指令集。

支持的函数操作:
存取操作(load/store/set)

set操作类型 描述
_mm256_setzero_ps/pd 返回一个全为0的float类型的向量
_mm256_setzero_si256 返回一个全为0的整型向量
_mm256_set1_ps/pd 返回一个float类型的数填充向量
_mm256_set1_epi8/epi16/epi32/epi64x 用整型数填充向量
_mm256_set_ps/pd 用8个float或4个double类型数字初始化向量
_mm256_set_epi8/epi16/epi32/epi64x 用一个整型数初始化向量
_mm256_set_m128/m128d/m128i 用2个128位的向量初始化一个256位向量
_mm256_setr_ps/pd 用8个float或4个double的转置顺序初始化向量
_mm256_setr_epi8/epi16/epi32/epi64x 用若干个整型的转置顺序初始化向量
load操作类型 描述
_mm256_load_ps/pd 从对齐的内存地址加载浮点向量
_mm256_load_si256 从对齐的内存地址加载整型向量
_mm256_loadu_ps/pd 从未对齐的内存地址加载浮点向量
_mm256_loadu_si256 从未对齐的内存地址加载整型向量
_mm_maskload_ps/pd 根据掩码加载128位浮点向量的部分
_mm256_mask_ps/pd 根据掩码加载256位浮点向量的部分
(2)_mm_maskload_epi32/64 根据掩码加载128位整型向量的部分
(2)_mm256_maskload_epi32/64 根据掩码加载256位整型向量的部分

PS:
最后两个函数前面有个(2),代表只在AVX2中支持

store函数:
image

算术运算

  • 加减法
数据类型 描述
_mm256_add_ps/pd 对两个浮点向量做加法
_mm256_sub_ps/pd 对两个浮点向量做减法
(2)_mm256_add_epi8/16/32/64 对两个整形向量做加法
(2)_mm256_sub_epi8/16/32/64 对两个整形向量做减法
(2)_mm256_adds_epi8/16 (2)_mm256_adds_epu8/16 两个整数向量相加且考虑内存饱和问题
(2)_mm256_subs_epi8/16 (2)_mm256_subs_epu8/16 两个整数向量相减且考虑内存饱和问题
_mm256_hadd_ps/pd 水平方向上对两个float类型向量做加法
_mm256_hsub_ps/pd 垂直方向上最两个float类型向量做减法
(2)_mm256_hadd_epi16/32 水平方向上对两个整形向量做加法
(2)_mm256_hsub_epi16/32 水平方向上最两个整形向量做减法
(2)_mm256_hadds_epi16 对两个包含short类型的向量做加法且考虑内存饱和的问题
(2)_mm256_hsubs_epi16 对两个包含short类型的向量做减法且考虑内存饱和的问题
_mm256_addsub_ps/pd 加上和减去两个float类型的向量

将饱和度考虑在内的函数将结果钳制到可以存储的最小/最大值。没有饱和的函数在饱和发生时忽略内存问题。
而在水平方向上做减法的意思如下图:
image
_mm256_hadd_pd接口描述

Operation
dst[63:0] := a[127:64] + a[63:0]
dst[127:64] := b[127:64] + b[63:0]
dst[191:128] := a[255:192] + a[191:128]
dst[255:192] := b[255:192] + b[191:128]
dst[MAX:256] := 0

最后一个指令:_mm256_addsub_ps/pd 在偶数位置减去,奇数位置加上,获最后的目标向量

  • 乘除法
数据类型 描述
_mm256_mul_ps/pd 对两个float类型的向量进行相乘
(2)_mm256_mul_epi32 (2)_mm256_mul_epu32 将包含32位整数的向量的最低四个元素相乘
(2)_mm256_mullo_epi16/32 Multiply integers and store low halves
(2)_mm256_mulhi_epi16 (2)_mm256_mulhi_epu16 Multiply integers and store high halves
(2)_mm256_mulhrs_epi16 Multiply 16-bit elements to form 32-bit elements
_mm256_div_ps/pd 对两个float类型的向量进行想除

image

image

  • 融合乘法和加法
数据类型 描述
(2)_mm_fmadd_ps/pd/ (2)_mm256_fmadd_ps/pd 将两个向量相乘,再将积加上第三个。(res=a*b+c)
(2)_mm_fmsub_ps/pd/ (2)_mm256_fmsub_ps/pd 将两个向量相乘,然后从乘积中减去一个向量。(res=a*b-c)
(2)_mm_fmadd_ss/sd 将向量中最低的元素相乘并相加(res[0]=a[0]*b[0]+c[0])
(2)_mm_fmsub_ss/sd 将向量中最低的元素相乘并相减(res[0]=a[0]*b[0]-c[0])
(2)_mm_fnmadd_ps/pd (2)_mm256_fnmadd_ps/pd 将两个向量相乘,并将负积加到第三个。(res = -(a * b) + c)
(2)_mm_fnmsub_ps/pd/ (2)_mm256_fnmsub_ps/pd 将两个向量相乘,并将负积加到第三个 (res = -(a * b) - c)
(2)_mm_fnmadd_ss/sd 将两个向量的低位相乘,并将负积加到第三个向量的低位。(res[0] = -(a[0] * b[0]) + c[0])
(2)_mm_fnmsub_ss/sd 将最低的元素相乘,并从求反的积中减去第三个向量的最低元素。(res[0] = -(a[0] * b[0]) - c[0])
(2)_mm_fmaddsub_ps/pd/ (2)_mm256_fmaddsub_ps/pd 将两个矢量相乘,然后从乘积中交替加上和减去(res=a*b+/-c)
(2)_mm_fmsubadd_ps/pd/ (2)_mmf256_fmsubadd_ps/pd 将两个向量相乘,然后从乘积中交替地进行减法和加法(res=a*b-/+c)(奇数次方,偶数次方)
  • 排列和洗牌(Permuting and Shuffling)
    排列 Permuting
数据类型 描述
_mm_permute_ps/pd _mm256_permute_ps/pd 根据8位控制值从输入向量中选择元素
(2)_mm256_permute4x64_pd/ (2)_mm256_permute4x64_epi64 根据8位控制值从输入向量中选择64位元素
_mm256_permute2f128_ps/pd 基于8位控制值从两个输入向量中选择128位块
_mm256_permute2f128_si256 基于8位控制值从两个输入向量中选择128位块
_mm_permutevar_ps/pd _mm256_permutevar_ps/pd 根据整数向量中的位从输入向量中选择元素
(2)_mm256_permutevar8x32_ps (2)_mm256_permutevar8x32_epi32 使用整数向量中的索引选择32位元素(浮点和整数)

image

洗牌Shuffling

数据类型 描述
_mm256_shuffle_ps/pd 根据8位值选择浮点元素
_mm256_shuffle_epi8/ _mm256_shuffle_epi32 根据8位值选择整数元素
(2)_mm256_shufflelo_epi16/ (2)_mm256_shufflehi_epi16 基于8位控制值从两个输入向量中选择128位块

对于_mm256_shuffle_pd,只使用控制值的高4位。如果输入向量包含int或float,则使用所有控制位。对于_mm256_shuffle_ps,前两对位从第一个矢量中选择元素,第二对位从第二个矢量中选择元素。
image

举例:

#include <iostream>
#include <immintrin.h>

void print_value(__m128 value)
{
    float *v = (float *)&value;
    std::cout<< v[0] << "," << v[1] << ","<< v[2] << ","<<v[3] << std::endl;
}

// load,用于从内存加载到寄存器中
__m128 function_load(float const*mem_addr)
{
    return _mm_load_ps(mem_addr);
}

// set,用于从内存加载到寄存器中
__m128 function_set(float const *mem_addr)
{
    return _mm_set_ps(mem_addr[3], mem_addr[2], mem_addr[1], mem_addr[0]);
}

// 直接初始化
__m128 direct_init(float const *mem_addr)
{
    __m128 value = {mem_addr[0], mem_addr[1], mem_addr[2], mem_addr[3] };
    return value;
}

int main()
{
    float *mem_addr = (float*)malloc(sizeof(float)*4);
    mem_addr[0] = 1.0f;
    mem_addr[1] = 2.0f;
    mem_addr[2] = 3.0f;
    mem_addr[3] = 4.0f;
    print_value(function_load(mem_addr));
    print_value(function_set(mem_addr));
    print_value(direct_init(mem_addr));

    return 0;
}

运行结果:
image

一个简单的加法验证SIMD效率上的提升

// 写一个简单的程序来验证SIMD在效率方面的提升
#include <iostream>
#include <nmmintrin.h>
#include <immintrin.h>
#include <string>
#include <time.h>
#include <stdlib.h>

void print_time_result(std::string func_name, clock_t start, clock_t finish, int res)
{
    double time = (double(finish - start) / double(CLOCKS_PER_SEC)) * 1000; // ms
    std::cout << func_name << ": " << time << "ms, result = " << res << std::endl;
}

// 普通的加法
void normal_add(int *nums, size_t n)
{
    clock_t start = clock();
    int sum = 0;
    for (size_t i = 0; i < n; i++)
    {
        sum += nums[i];
    }
    clock_t finish = clock();
    print_time_result("normal_add", start, finish, sum);
}

// 使用128bit的SIMD
void SSE_add(int *nums, size_t n)
{
    clock_t start = clock();

    __m128i simd_sum = _mm_setzero_si128();// 累加和
    int normal_sum = 0;

    size_t loop = n / 4;
    size_t reserve = n % 4;

    __m128i *p = (__m128i *)nums;
    for (size_t i = 0; i < loop; i++)
    {
        simd_sum = _mm_add_epi32(simd_sum, *p);
        p++;
    }

    int *q = (int *)p;
    for (size_t i = 0; i < reserve; i++)
    {
        normal_sum += q[i];
    }
    normal_sum += (((int *)&simd_sum)[0] + ((int *)&simd_sum)[1] + ((int *)&simd_sum)[2] + ((int *)&simd_sum)[3]);

    clock_t finish = clock();
    print_time_result("SSE_add", start, finish, normal_sum);
}

// 使用256bit的SIMD
void AVX2_add(int *nums, size_t n)
{
    clock_t start = clock();

    __m256i simd_sum = _mm256_setzero_si256();
    __m256i simd_load;
    int normal_sum = 0;

    size_t loop = n / 8;
    size_t reserve = n % 8;

    __m256i *p = (__m256i *)nums;
    for (size_t i = 0; i < loop; i++)
    {
        simd_load = _mm256_load_si256((const __m256i *)p);
        simd_sum = _mm256_add_epi32(simd_sum, simd_load);
        p++;
    }

    int *q = (int *)p;
    for (size_t i = 0; i < reserve; i++)
    {
        normal_sum += q[i];
    }
    normal_sum += (((int *)&simd_sum)[0] + ((int *)&simd_sum)[1] +
                   ((int *)&simd_sum)[2] + ((int *)&simd_sum)[3] +
                   ((int *)&simd_sum)[4] + ((int *)&simd_sum)[5] +
                   ((int *)&simd_sum)[6] + ((int *)&simd_sum)[7]);

    clock_t finish = clock();
    print_time_result("AVX2_add", start, finish, normal_sum);
}

int main()
{
    size_t n = 10000000;
    int *nums = NULL;
    // nums = (int *)malloc(sizeof(int) * n);
    posix_memalign((void **)&nums, 32, sizeof(int) * n); // 注意要32字节对齐,否则会段错误

    for (size_t i = 0; i < n; i++)
    {
        nums[i] = 5;
    }
    normal_add(nums, n);
    SSE_add(nums, n);
    AVX2_add(nums, n);
    return 0;
}

上述代码其实就是在做10000000个5相加,normal_add()是按照一般思路,循环1千万次加法,SSE_add()是4个一组(128bit)相加,AVX2_add()是8个一组(256bit)相加。

注意:对于SSE,我们申请的空间要16字节对齐,对于AVX要32字节对齐,否则会出现段错误。

// 方法一:
__attribute__((aligned(16))) float A[10000000];// Linux下
// 方法二:
posix_memalign((void **)&B, 32, sizeof(int) * 10000000); // #include <stdlib.h>

运行结果:
image
也许和硬件不一样,在自己电脑上还是有提升的。

参考博客
https://blog.triplez.cn/avx-avx2-learning-notes/
https://blog.csdn.net/just_sort/article/details/94393506
https://zhuanlan.zhihu.com/p/457505686

标签:mm256,ps,pd,Intel,__,指令集,SIMD,128,向量
From: https://www.cnblogs.com/whiteBear/p/17570001.html

相关文章

  • Java开发笔记之mac的intellij idea在debug模式下卡住的问题
    0x00问题描述mac的Intellijidea在debug模式下放行时,程序会卡住无响应;即使在已经放行的情况下,后续代码也不运行,console内只显示Theapplicationisrunning或者等了很久程序才开始后续的运行。 0x01解决方案修改host中的配置。通过以下命令,打开hosts的编辑页面。sudovi......
  • Tool-Intel VTune Profiler
    Tool-IntelVTuneProfiler转自使用IntelVTuneProfiler进行性能分析及优化初识Intel®VTune™ProfilerIntelVTuneProfiler是一个全平台的性能分析工具,可以帮助你快速发现和分析应用程序及整个系统的性能瓶颈。工具支持分析本地或远程的Windows,Linux及Android应用,这些应......
  • CPU与指令集
    CPU与指令集一、CPUCPU是指CentralProcessingUnit,即中央处理器。是计算机的运算和控制中心,其功能主要是执行程序指令并计算相关数据。CPU主要由控制单元、运算单元、存储单元等组成。控制单元:负责将程序指令转化成硬件电路中的实际动作。比如打开某个加法器或减法器等等。......
  • Mac版多平台Java开发工具JetBrains IntelliJ IDEA 2023
    JetBrainsIntelliJ是一个多平台的Java开发工具,可以用于Java开发。它可以帮助您在Linux、Windows、Mac和Linux上开发基于Java的应用程序、软件和服务。它还提供了一个跨平台的工具包,可以为开发者提供Java开发者的基础设施设计支持。JetBrainsIntelliJ与Linux有很多相似之处:Java......
  • IntelliJ IDEA配置GitHub上传项目
    保证本地已安装\(Git\)一、配置GitHub账号新建项目,\(File\rightarrowSettings\rightarrowVersion\Control\rightarrowGitHub\)添加\(GitHub\)账号二、配置公钥打开\(GitBash\)输入命令ssh-keygen-t-rsa-C"PersonalEmail",将根据指示到对应的路径(一般是C:\Us......
  • Intel真干不过 迷你主机价格屠夫:酷睿i7+24GB+2TB到手2999元
    前几天Intel宣布放弃迷你电脑NUC业务,已经授权给华硕公司接手了,要知道Intel的NUC电脑价格已经是市场上的天花板,结果还做不下去,毕竟国产迷你主机把价格已经打穿地板了,酷睿i7+24GB+2TB这样的可以做到2999元。GMK极摩客K3Pro迷你电脑现在优惠400元,多个配置都出现了好价,具体如下:24G+......
  • Intellij IDEA 显示 access.log 日志
    先配置  SpringBoot记录access.log日志,先让accesslog 显示出来  ......
  • 工具 --- IL指令集解释
    引言汇总一下所有的.NETIL指令,以及它们的名称、操作码值、堆栈转换行为和描述。作为反编译IL代码时的查询字典。IL指令集列表以下内容来自微软官方文档,通过百度翻译API翻译为中文。指令英文描述中文描述AddAddstwovaluesandpushestheresultontotheev......
  • Intellij Idea技巧-1
    快捷键下面这个idea和eclipse快捷键的对比,能帮助eclipse的开发者更快适应idea。很多人对idea的不适应都来自快捷键这一层次的基本操作习惯的不适应,只要过了这一关,就进入了投奔idea的快车道。参考:https://www.catalysts.cc/en/wissenswertes/intellij-idea-and-eclipse-shortcuts/另......
  • C/C++ 宏获取当前编译程序工作的CPU指令集平台(综合大全覆盖各类CPU)
    参考:https://blog.csdn.net/liulilittle/article/details/126644547?spm=1001.2101.3001.6650.6&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-6-126644547-blog-43935465.235%5Ev38%5Epc_relevant_default_base3&dep......