SSE與AVX指令集
SSE
指令集是英特爾提供的基于SIMD
(單指令多數(shù)據(jù)钥勋,也就是說同一時間內,對多個不同的數(shù)據(jù)執(zhí)行同一條命令)的硬件加速指令勺馆,通過使用寄存器來進行并行加速微猖。經過幾代的迭代晓避,最新的SSE4
已經極大地擴展了指令集的功能业踏,并且隨后已經從128位寄存器繼續(xù)擴展到256位的指令盛撑。
想要使用SSE或AVX指令集檬姥,需要包含以下頭文件
#include <mmintrin.h> //mmx, 4個64位寄存器
#include <xmmintrin.h> //sse, 8個128位寄存器
#include <emmintrin.h> //sse2, 8個128位寄存器
#include <pmmintrin.h> //sse3, 8個128位寄存器
#include <smmintrin.h> //sse4.1, 8個128位寄存器
#include <nmmintrin.h> //sse4.2, 8個128位寄存器
#include <immintrin.h> // avx, 16個256位寄存器
1. intrinsics
intrinsic是將xmm曾我、sse等指令封裝,變成內聯(lián)函數(shù)以減少函數(shù)調用的一種操作健民,具體語法如下:
#pragma intrinsic(function_name)
intrinsic只允許內聯(lián)諸如標準庫函數(shù)或部分函數(shù)抒巢,是通過內聯(lián)底層標準函數(shù)而減小開銷的,不是所有函數(shù)都能使用秉犹。而指令集SSE蛉谜、AVX等屬于封裝好的標準內聯(lián)函數(shù),導入頭文件之后可直接使用崇堵。
2. SSE指令集
完整的SSE指令集可以點擊此處查看悦陋。
我們主要關注SSE指令集在C和C++上的應用。在工程中筑辨,對于128位的寄存器俺驶,最實用的操作就是當做4個32位單精度的浮點數(shù)。其中棍辕,包裝指令集
是指矢量指令集暮现,單個指令會對VALU中的數(shù)據(jù)都進行同一指令操作;而標量指令
是指指令只對寄存器最低位的數(shù)據(jù)進行操作楚昭。
以下是常用的函數(shù)栖袋。
-
編譯語句
g++ -msse4 filename.cpp
-
編程實例
對于多核處理器,每一個核都有著自己的緩存抚太,以及FPU塘幅、VALU模塊昔案。VALU允許同時操作4個浮點數(shù),通過SSE指令集加速一個128位矢量的FDTD程序电媳。
-
相加的簡例
/* 對于變量v1與v2各有x踏揣、y、z匾乓、w四個屬性捞稿,vec_res的結果便是v1、v2對應的屬性相加 */ // 標量版本 vec_res.x = v1.x + v2.x; vec_res.y = v1.y + v2.y; vec_res.z = v1.z + v2.z; vec_res.w = v1.w + v2.w; // VALU版本 movaps xmm0, [v1]; // 將要移動v1變量到xmm0寄存器中 xmm0 = v1.w | v1.z | v1.y | v1.x ; // 將4個值加載到寄存器中 addps xmm0, [v2]; // 將要對xmm0和v2變量進行相加 xmm0 = v1.w + v2.w | v1.z + v2.z | v1.y + v2.y | v1.x + v2.x ; // 相加 movaps [vec_res], xmm0; // 將寄存器的值賦給vec_res
-
C++矢量相乘簡例
注意:在編譯時必須使用
g++
編譯器拼缝,同時娱局,SSE指令集有SSE
、SSE2
咧七、SSE3
衰齐、SSE4
幾種,越新的版本功能就越多继阻,可以通過在使用g++
編譯鏈接時娇斩,加上-msse4
使用SSE4
指令集,其他以此類推穴翩。/* 使用SSE指令進行矢量相乘加速 */ #include<iostream> // 使用SSE指令集需要的頭文件 #include<xmmintrin.h> using namespace std; int main() { // VALU加速版本: 0m0.004s __m128 a, b; a = _mm_set_ps(1, 2, 3, 4); b = _mm_set_ps(1, 2, 3, 4); __m128 c = _mm_add_ps(a, b); for(int i=0; i<4; i++) { cout << a[i] << endl; } return 0; }
-
3. 擴展后的AVX指令集
-
新增特性
- 將 128 位 SIMD 寄存器擴展至 256 位。
- 添加了 3 操作數(shù)非破壞性運算锦积。之前在 A = A + B 類運算中執(zhí)行的是 2 操作數(shù)指令芒帕,它將覆蓋源操作數(shù),而新的操作數(shù)可以執(zhí)行 A = B + C 類運算丰介,且保持原始源操作數(shù)不變背蟆。
需要啟用AVX指令時,編譯必須加上
-mvax
哮幢,否則會報錯带膀。頭文件中包含的所有函數(shù)在 此處 可以查看。
-
編譯語句:
g++ -mavx filename.cpp
-
YMM寄存區(qū)
相比于早年128位的XMM寄存器橙垢,英特爾AVX提供了256位的YMM寄存器垛叨,而XMM被視作了相應的底層部分。
-
對齊
當源數(shù)據(jù)是關于n位對齊(也就是能完整地以n為一個單位切分)地存入YMM寄存器中柜某,稱之為數(shù)據(jù)對齊嗽元。對于SSE運算來說,默認必須保證數(shù)據(jù)對齊(雖不必須喂击,但最好保證剂癌,某些操作并不提供非對齊的操作版本)。
-
盡量不要VEX與XMM指令混用
混合使用舊的僅 XMM 的指令和較新的AVX 指令會導致延遲 翰绊,所以不要將 VEX 前綴的指令和非 VEX 前綴的指令混合使用佩谷,以實現(xiàn)最佳吞吐量 旁壮。
-
相加的例子
#include<iostream> #include<immintrin.h> // avx using namespace std; int main() { __m256 a, b; /* Note: 隨著位數(shù)的變化,寄存器可以存放的同一類型數(shù)據(jù)的個數(shù)也發(fā)生了翻倍谐檀, 在128位的SSE中抡谐,_mm_set_ps()可以計算4個float型數(shù)據(jù),而到了 256位的AVX中稚补,_mm256_set_ps()可以計算8個float型數(shù)據(jù)童叠。 */ a = _mm256_set_ps(1, 2, 3, 4, 5, 6, 7, 8); b = _mm256_set_ps(1, 2, 3, 4, 5, 6, 7, 8); __m256 c = _mm256_add_ps(a, b); for(int i=0; i<8; i++) { cout << c[i] << endl; } return 0; }