AVX512

最近接触到SIMD编码,就不可避免的查到了AVX指令集,两者有什么关系呢,了解一下?

问:AVX是什么?
答:是一套指令集

下面具体看:

AVX#

以下内容主要转载自:AVX指令集是什么?它的应用又有哪些?,梳理的很清晰。

要搞明白AVX指令集的作用,首先要讲明白它是什么。定义很简单,它就是x86处理器上面的一套SIMD指令集,是经典的SSE系列指令集的直接继承者。那么SIMD又是什么呢?

SIMD#

在计算机刚刚出现的早期阶段,冯·诺伊曼式计算机每次输入一个指令只能够操作一对数据,比如说"+,a,b"可以让ab进行相加,这就是单指令流单数据流(Single Instruction Stream, Single Data Stream)。显然,在面对大量数据的时候,这种操作数据的方法效率较低,程序员想要让一次操作就对多组数据生效,怎么办呢?
单指令流多数据流操作Single Instruction Stream, Multiple Data Stream)的思路就被引入了,它让输入一次指令就操作多组数据变成了可能。下图更直观:


或者用代码表示:

//未使用SIMD
vectorAdd(const float* a, const float* b, const float* c){
    for(int i=0; i<8; i++) {
        c[i] = a[i] + b[i];  //一条代码仅能操作2个单个值进行运算
    }                        //需要重复循环操作才能运算完整个数组间的加法
}
//使用AVX
__m256 vectorAdd(__m256 a, __m256 b, __m256 c) {
    return _mm256_add_ps(a,b); //一个方法可以同时操作整个数组进行运算 - 一步到位
}

AVX的发展#

MMX#

上世纪八九十年代,很多处理器开发商都意识到了SIMD的前景,他们开始往自家的处理器里加入SIMD支持。1996年,Intel发布了基于新版P55C架构(最早一版Pentium处理器为P5架构)的Pentium MMX(下图)系列处理器,其中引入了新的MMX指令集,开始支持SIMD。

总结:
(1)MMX的优点
首次支持SIMD操作的指令集,提升了效率

SSE#

Pentium MMX系列处理器上新引入的MMX指令集开创了x86处理器支持SIMD操作的先河,该指令集定义了8个64-bit宽度的寄存器,每个寄存器的64-bit容量中可以放入八个8-bit长度的整数或四个16-bit长度整数或两个32-bit整数,CPU在识别到MMX指令集的新指令时会自动将寄存器中的数据进行分割计算,这样一来,单个指令就成功操作了多个数据,实现了SIMD。

但MMX毕竟太嫩,它实际上是通过复用CPU内部x87浮点单元的寄存器来实现SIMD的,所以与运行浮点运算的x87指令集有冲突,两者不能交叉使用,必须先进行切换。另外,由于上述的冲突,它只支持整数操作,在即将要到来的3D时代中显得有些不够用。

Intel当然很清楚MMX指令集的局限之处,而竞争对手新捣鼓出来的3DNow!指令集(1998年,AMD K6-2)已经支持了浮点SIMD运算,于是他们赶紧在经典的奔3处理器上面引入了新的SSE(Streaming SIMD Extensions)指令集,时间点为1999年2月份。

总结:
(1)MMX的缺点
不支持浮点数运算(只支持整数)

(2)SSE的优点
支持浮点数运算
寄存器扩展到128bit,计算效率更高

AVX#

SSE指令集解决了MMX指令集身上存在的两大问题,通过引入新的独立寄存器解决了与浮点运算间的冲突问题,同时也就支持了浮点SIMD运算。当然它相对于MMX有很大加强,表现在它的寄存器宽度随着处理器架构的进步而达到了128-bit,这样一来一次SIMD指令能够操作更多的数据,效率上有大幅度的提高。不过初代SSE指令集的单个寄存器只支持32-bit长度的浮点数,还是有很大的局限性,这个问题在Pentium 4(Willamette,2000年)上面引入的SSE2中被解决了,SIMD操作的灵活度高了很多。

随后在约莫8年的时间里,Intel一直在更新SSE指令集,从SSE出到SSE4,AMD方面则是一直在跟进,到了SSE4.2,AMD开始想要在指令集上面寻找自己的翻身点,于是推出了只有自家支持的SSE4a子集,随后更是提前于Intel提出了SSE5。

但Intel不干,我是x86的老大,我不能跟着你来。他们另起炉灶,准备在未来的Sandy Bridge架构中引入一套新的SIMD指令集,这套新指令集在2008年公布,被命名为高级向量扩展(Advanced Vector Extensions)。

相比起迭代了多年的SSE系列指令集,AVX指令集带来了巨大的革新,其中最为主要的是,它在兼容SSE指令集性的同时,将SSE时代最大宽度为128-bit的寄存器拓宽到了256-bit。

不过初代AVX指令集还是比较保守的,它没有将所有指令宽度拓宽到256-bit,而是选择停留在128-bit上面。全面进入256-bit时代这个任务,还是交给了随后的Haswell架构来完成(2013年6月份)。

下图是同处理器用不同指令集的能效对比

但如果以为Intel会就此停下脚步的话,那就大错特错了,他们很快捣鼓出了更宽的AVX-512指令集,顾名思义,其寄存器宽度再次加倍,来到512-bit。

首个支持AVX-512指令集的处理器其实是Intel的Xeon Phi加速卡,首次跑到CPU上已经是Skylake-X系列了。而AVX-512也并不再是一个单一的指令集,它实际上指代的是多个指令集的集合,目前这个数字是17,之后可能还会增多。所有支持AVX-512的处理器都必须支持AVX-512 Foundation子集,从命名上也可以看出,它其实是AVX-512指令集的基础。

AVX-512子集列表

现在是19了

目前只有基于Skylake-Server和Ice Lake这两个架构的处理器可以支持AVX-512(Cannon Lake死了,不然也算),使用门槛较高,一般新一点的应用也只是针对AVX2进行优化。

宽度越大,处理器的计算能力也就越强,尤其是在浮点运算方面,理论上提升有一倍之多,而实际应用中,如果优化得当,其提升幅度还要大一些。但是,新指令集在带来性能增长的同时也带来了另一个让人感到头痛的问题——功耗。

总结:
(1)SSE的缺点
寄存器支持最大宽度为128bit
(2)AVX的优点
寄存器最大支持512bit,叫做AVX-512。计算性再次提升!

AVX的缺点#

功耗大

AVX指令集在带来更高性能的同时让CPU的峰值功耗也变高了,可以通过下面的例子进行理解:

飞机发动机是按照最大起飞重量设计的,如果实际的载重没有到最大起飞重量的话,飞行员就可以减推力起飞来降低油耗。CPU也是一样的,最吃功耗的执行单元是根据最大宽度来设计的,平时用不到最大宽度的时候它的功耗就小了,而一旦用到极限,它也就会全开,此时CPU的功耗就上去了。

现如今CPU的功耗是根据负载大小来的,在同频下面,AVX2的负载明显高于SSE负载,因此它的功耗也会大上去。为了让CPU的功耗保持在TDP范围之内,Intel特地设计了一个AVX偏移频率,让工作在AVX状态下面的处理器降低一点频率以减小发热量和功耗,保证使用安全。Intel官方也在2014年的一份AVX指令集优化白皮书中明确说明使用AVX指令集需要额外的电压和电流。

AVX的应用#

对于我们这些要做跑分评测的编辑来说,最常接触到的AVX应用其实就是AIDA64了,那么可能有读者就要问了,这个指令集都已经推出十年了难道只能用来跑分烤机吗?当然不是,在Intel的推广之下,现如今已经有大量的生产力应用支持它了,主要在渲染、视频编码、加解密和数学计算等方面有应用,新的AVX-512还针对深度学习推出了AVX-512 VNNI子集,另外,普通玩家最为关心的游戏方面也是有越来越多的应用了,下面举几个例子。

AIDA64是一款测试软硬件系统信息的工具,它可以详细的显示出PC的每一个方面的信息。AIDA64不仅提供了诸如协助超频,硬件侦错,压力测试和传感器监测等多种功能,而且还可以对处理器,系统内存和磁盘驱动器的性能进行全面评估。

渲染、视频编码#

渲染方面最常见的有Blender,它不仅仅在我们的测试中被用的多,是真的有很多人都会用它做动画或者CG图,它的渲染引擎可以调用AVX2指令集进行加速计算,吃满你的CPU。

Blender是一款免费开源三维图形图像软件,提供从建模、动画、材质、渲染、到音频处理、视频剪辑等一系列动画短片制作解决方案。

跟渲染方面有点搭边的就是视频编码了,x264和x265这两个知名开源视频编码器想必已经不用再多介绍了,它们都在前几年中纷纷加入了对于AVX指令集的支持,后者甚至加入了针对AVX-512的支持,不过还需要继续优化。另外,Intel方面自己也开源了一套名为SVT的视频编码器,配合不同后端可以实现不同的编码,对AVX和多核的优化相当好。

深度学习#

深度学习方面,Google著名的开源深度学习框架Tensorflow在1.6版本之后就已经需要一颗支持AVX指令集的CPU了,换言之,它应用了AVX指令集。

另外,AVX-512的大宽度让它很适合用来跑深度学习,所以Intel也针对深度学习设计了一套子指令集——AVX-512 VNNI,用来加速深度学习相关的计算,在测试中,它表现出了相当的实力。

加解密#

加解密计算场景中对CPU的计算吞吐量有较大的要求,此时AVX指令集就可以发挥作用,常见的软件支持就有OpenSSL这个堪称是互联网基石的加密库,另外像很多程序会使用的libsodium加密库也提供了从AVX到AVX-512的优化,而Linux内核也支持使用AVX和AVX2指令集进行加解密计算,还会配合AES-NI这个专用的指令集。实际上目前还有很多数字货币的计算过程支持使用AVX指令集,不过这个应该是真的没有人会用了

一般做加解密计算在GPU上更快,不知道GPU是否支持SIMD?

游戏#

近两三年的大作基本都开始启用AVX指令集来进行计算了,一般在游戏中CPU负责除了图形以外的杂活,比如说计算各种NPC的运动路径,计算各种动体的轨迹这样的杂活。不过近两年也有厂商想让Intel参与进游戏图形计算,甚至是当下热门的光线追踪运算,比如Intel的光线追踪计算库Embree就可以被整合进游戏中,目前已经有《坦克世界》等游戏使用了它,Embree库高度依赖AVX指令集,也对CPU的游戏性能提出了新的阐述方式:直接参与图形渲染。

AVX512的使用#

(1)查询Intel支持的所有指令集:链接

(2)查询mac支持的指令集

sysctl -a | grep machdep.cpu.features

(3)AVX512的函数:链接

基础#

数据类型#

函数#

举例#

参考:https://lusing.blog.csdn.net/article/details/130334103?spm=1001.2014.3001.5502

  • SIMD
    image

虽然多线程和OpenMP看起来都不错,都容易编程,但是,我们的优化并不是以简化编程为目的的。

虽然我们抱怨Intel是牙膏厂,每年的进步越来越有限。不过,还总是有新的指令增加到新的架构中来。这其中就有越来越强大的SIMD指令。

  • AVX

SIMD就是一条机器指令可以实现多条数据的操作。在Intel平台上,早在1997年就推出了64位的MMX指令集。1999年又有了128位的SSE指令集。2011年,又推出了256位的AVX(Advanced Vector Extensions)指令,我们来个例子看看:

// main.cpp
#include <iostream>
#include <immintrin.h> // 包含 AVX 指令集头文件

void matrix_addition_avx(float* A, float* B, float* C, int size) {
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j += 8) { // 每次处理 8 个元素(AVX 可以处理 256 位,即 8 个单精度浮点数)
            __m256 vecA = _mm256_loadu_ps(&A[i * size + j]);
            __m256 vecB = _mm256_loadu_ps(&B[i * size + j]);
            __m256 vecC = _mm256_add_ps(vecA, vecB);
            _mm256_storeu_ps(&C[i * size + j], vecC);
        }
    }
}

int main() {
    int size = 8; // 假设矩阵大小为 8x8
    float A[64] = { /* ... */ }; // 初始化矩阵 A
    float B[64] = { /* ... */ }; // 初始化矩阵 B
    float C[64] = { 0 }; // 结果矩阵 C

    matrix_addition_avx(A, B, C, size);

    // 输出结果
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            std::cout << C[i * size + j] << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}

我们来解释一下使用SIMD的几条语句:

__m256 vecA = _mm256_loadu_ps(&A[i * size + j]):从矩阵 A 中加载 8 个浮点数(一次性处理 256 位数据),存储在一个名为 vecA 的 __m256 类型变量中。

__m256 vecB = _mm256_loadu_ps(&B[i * size + j]):同样地,从矩阵 B 中加载 8 个浮点数,存储在一个名为 vecB 的 __m256 类型变量中。

__m256 vecC = _mm256_add_ps(vecA, vecB):使用 AVX 指令 _mm256_add_ps 对 vecA 和 vecB 中的浮点数分别进行逐元素加法,并将结果存储在名为 vecC 的 __m256 类型变量中。

_mm256_storeu_ps(&C[i * size + j], vecC):将 vecC 中的 8 个加法结果存储回矩阵 C 的相应位置。

这段代码使用了 AVX 指令集,实现了对浮点矩阵的加法运算。请注意,为了充分利用 AVX 的并行处理能力,矩阵尺寸应该是 8 的倍数。如果矩阵尺寸不是 8 的倍数,需要添加额外的逻辑来处理剩余的元素。

  • AVX2

后来,Intel又推出了AVX2指令集,不过对于我们上边的代码并没有太多优化,而主要优化是在整数方面,我们这次使用AVX2提供的整数计算的加速来实现:

#include <iostream>
#include <immintrin.h> // 包含 AVX2 指令集头文件

void matrix_addition_avx2_int(int *A, int *B, int *C, int size) {
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j += 8) { // 每次处理 8 个元素(AVX2 可以处理 256 位,即 8 个 int32 整数)
            __m256i vecA = _mm256_loadu_si256((__m256i *)&A[i * size + j]);
            __m256i vecB = _mm256_loadu_si256((__m256i *)&B[i * size + j]);
            __m256i vecC = _mm256_add_epi32(vecA, vecB);
            _mm256_storeu_si256((__m256i *)&C[i * size + j], vecC);
        }
    }
}

int main() {
    int size = 8; // 假设矩阵大小为 8x8
    int A[64] = { /* ... */ }; // 初始化矩阵 A
    int B[64] = { /* ... */ }; // 初始化矩阵 B
    int C[64] = {0}; // 结果矩阵 C

    matrix_addition_avx2_int(A, B, C, size);

    // 输出结果
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            std::cout << C[i * size + j] << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}

我们不惜折腾量化一把转换成整数的原因是,AVX中只有_mm_add_epi32指令,只能对两个128位整数向量的逐元素相加,而_mm256_add_epi32是256位,数据量加倍了。

不只是加法,AVX2 提供了一系列针对整数操作的新指令,例如乘法、位操作和打包/解包操作等

AVX2指令的执行吞吐量(throughput)一般为1指令/周期,而AVX1为2指令/周期。所以在同频率下,AVX2的整数加法指令性能理论上可以提高一倍

同时, 与其他AVX2指令结合使用,如_mm256_load_si256或_mm256_store_si256等,来从内存中加载或存储向量,这样可以提高内存访问的性能和带宽。

  • AVX512

后来,Intel还推出了AVX512指令,基本上就把AVX1中的256换成512就可以了:

#include <iostream>
#include <immintrin.h> // 包含 AVX-512 指令集头文件

void matrix_addition_avx512(float *A, float *B, float *C, int size) {
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j += 16) { // 每次处理 16 个元素(AVX-512 可以处理 512 位,即 16 个单精度浮点数)
            __m512 vecA = _mm512_loadu_ps(&A[i * size + j]);
            __m512 vecB = _mm512_loadu_ps(&B[i * size + j]);
            __m512 vecC = _mm512_add_ps(vecA, vecB);
            _mm512_storeu_ps(&C[i * size + j], vecC);
        }
    }
}

int main() {
    int size = 16; // 假设矩阵大小为 16x16
    float A[256] = { /* ... */ }; // 初始化矩阵 A
    float B[256] = { /* ... */ }; // 初始化矩阵 B
    float C[256] = {0}; // 结果矩阵 C

    matrix_addition_avx512(A, B, C, size);

    // 输出结果
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            std::cout << C[i * size + j] << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}

但是,优化并不总是一根筋地往上堆指令就可以的,AVX512是一种非常耗电的指令集,此时我们需要实测权衡一下。

image

针对手机上用的ARM CPU,可以使用NEON指令来实现SIMD功能:

#include <stdio.h>
#include <arm_neon.h>

void matrix_addition_neon(float *A, float *B, float *C, int size) {
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j += 4) { // 每次处理 4 个元素(NEON 可以处理 128 位,即 4 个单精度浮点数)
            float32x4_t vecA = vld1q_f32(&A[i * size + j]);
            float32x4_t vecB = vld1q_f32(&B[i * size + j]);
            float32x4_t vecC = vaddq_f32(vecA, vecB);
            vst1q_f32(&C[i * size + j], vecC);
        }
    }
}

int main() {
    int size = 4; // 假设矩阵大小为 4x4
    float A[16] = { /* ... */ }; // 初始化矩阵 A
    float B[16] = { /* ... */ }; // 初始化矩阵 B
    float C[16] = {0}; // 结果矩阵 C

    matrix_addition_neon(A, B, C, size);

    // 输出结果
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            printf("%f ", C[i * size + j]);
        }
        printf("\n");
    }

    return 0;
}

参考#

1、AVX / AVX2 指令编程
2、simd指令集使用入门

作者:Hang Shao

出处:https://www.cnblogs.com/pam-sh/p/16210114.html

版权:本作品采用「知识共享」许可协议进行许可。

声明:欢迎交流! 原文链接 ,如有问题,可邮件(mir_soh@163.com)咨询.

posted @   PamShao  阅读(4760)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· PPT革命!DeepSeek+Kimi=N小时工作5分钟完成?
· What?废柴, 还在本地部署DeepSeek吗?Are you kidding?
· DeepSeek企业级部署实战指南:从服务器选型到Dify私有化落地
· 程序员转型AI:行业分析
历史上的今天:
2020-04-30 数据库:完整性
2020-04-30 计网:数据链路层
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu