指令集简介

本文参考:SSE指令学习笔记_grafx的博客-CSDN博客

                  在C/C++代码中使用SSE等指令集的指令(4)SSE指令集Intrinsic函数使用_gengshenghong的博客-CSDN博客

                  百度安全验证

AVX是什么?AVX指令集技术与应用解析 - 超能网

AVX指令集__mm256_setzero_ps_shang_ch的博客-CSDN博客

  • 指令集函数官网帮助文档

Intrinsics Guide,可在下图位置下载离线版本

AVX兼容所有SSE指令,但是不兼容MMX指令

 

__m256i是由整型构成的向量,charshortintlong均属于整型,所以__m256i就可以由32个char,或者16个short,或者8个int,又或者4个long构成,这些整型可以是有符号类型也可以是无符号类型。 

  • SSE简介

SIMD(Single Instruction Multiple Data)是单指令多数据技术,目前Intel处理器支持的SIMD技术包括MMX,SSE,AVX。

SSE(Stream SIMD Extentions,数据流单指令多数据扩展)是英特尔继MMX(Multi Media eXtension,多媒体扩展指令集)之后推出的新一代CPU指令集。MMX提供了8个64bit的寄存器进行SIMD操作,SSE系列提供了8个128bit的寄存器进行SIMD操作。而最新的AVX指令则支持256bit的SIMD操作。

如下图所示,SSE新增的8个128位寄存器(xmm0 ~ xmm7),每个寄存器可以用来存放4个32位单精度浮点数,8个16位整型数。也就是说,SSE中的所有计算都是一次性针对4个浮点数来完成的,这种批处理会带来显著的效率提升。使用SSE优化之后,我们的代码不一定会得到4倍速的提升,因为编译器可能已经自动对某些代码进行SSE优化了。

  • 如何使用SSE指令

       使用SSE指令有两种方式:一是直接在C/C++中嵌入(汇编)指令;二是使用Intel C++ Compiler或是Microsoft Visual C++中提供的支持SSE指令集的intrinsics内联函数。从代码可读和维护角度讲,推荐使用intrinsics内联函数的形式。intrinsics是对MMX、SSE等指令集的一种封装,以函数的形式提供,使得程序员更容易编写和使用这些高级指令,在编译的时候,这些函数会被内联为汇编,不会产生函数调用的开销。想要使用SSE指令,则需要包含对应的头文件:

#include <mmintrin.h>   //mmx
#include <xmmintrin.h>  //sse
#include <emmintrin.h>  //sse2
#include <pmmintrin.h>  //sse3
  • SSE指令的格式

SSE指令通常由三部分构成:

第一部分为前缀_mm(多媒体扩展指令集),表示该函数属于SSE指令集

第二部分为指令的操作类型,如_add、_mul等

第三部分通常由两个字母组成。第一个字母表示对结果变量的影响方式,为p或s。                                                                          p(packed:包裹指令) :该指令对xmm寄存器中的每个元素进行运算,即一次对四个浮点数(data0~data3)均进行计算;
   s(scalar:标量指令):该指令对寄存器中的第一个元素进行运算,即一次只对xmm寄存器中的data0进行计算。

第二个字母表示参与运算的数据类型,s表示32位浮点数,d表示64位浮点数,i32表示带符号32位整型,i64表示带符号64位整型,u32表示无符号32位整型,以此类推。由于SSE只支持32位浮点数的运算,所以你可能会在这些指令封装函数中找不到包含非s修饰符的,但你可以在MMX和SSE2的指令集中去认识它们。

_pixx(xx为长度,可以是8,16,32,64)packed操作所有的xx位有符号整数,使用的寄存器长度为64位;_epixx(xx为长度)packed操作所有的xx位的有符号整数,使用的寄存器长度为128位;_epuxx packed操作所有的xx位的无符号整数;

  • SSE的数据类型

SSE指令中intrinsics函数的数据类型为:__m128(单精度浮点数),如果使用sizeof(__m128)计算该类型大小,结果为16,即等于四个浮点数长度。__declspec(align(16))做为数组定义的修释符,表示该数组是以16字节为边界对齐的,因为SSE指令大部分支持这种格式的内存数据。他的定义如下:

typedef struct __declspec(intrin_type) __declspec(align(16)) __m128 {
   float m128_f32[4];
} __m128;

除__m128外、还包括__m128d(双精度浮点数)和__m128i(整型)。其中__m128i是一个共用体类型,其定义如下 :

typedef union __declspec(intrin_type)_CRT_ALIGN(16)__m128i {
	__int8                m128i_i8[16];  //char
	__int16               m128i_i16[8];  //short
	__int32               m128i_i32[4];  //int
	__int64               m128i_i64[2];  //long long
	unsigned __int8       m128i_u8[16];  //uchar
	unsigned __int16      m128i_u16[8];  //ushort
	unsigned __int32      m128i_u32[4];  //uint
	unsigned __int64      m128i_u64[2];  //ulonglong
}__m128i;
  • 常用SSE指令

load系列,用于加载数据(从内存到暂存器),大部分需要16字节对齐

__m128  _mm_load_ss(float *p) //将一个单精度浮点数加载到寄存器的第一个字节,其它三个字节清零(r0 := *p, r1 := r2 := r3 := 0.0)

__m128  _mm_load_ps(float *p) //将四个单精度浮点数加载到寄存器(r0 := p[0], r1 := p[1], r2 := p[2], r3 := p[3])

__m128  _mm_load1_ps(float *p)//将p地址的值加载到暂存器的四个字节,需要多条指令完成。从性能考虑,在内层循环不要使用这类指令(r0 := r1 := r2 := r3 := *p)

__m128  _mm_loadh_pi(__m128 a, __m64 *p)//
__m128  _mm_loadl_pi(__m128 a, __m64 *p)//

__m128  _mm_loadr_ps(float *p)//以_mm_load_ps反向的顺序加载,需要多条指令完成。(r0 := p[3], r1 := p[2], r2 := p[1], r3 := p[0])

__m128  _mm_loadu_ps(float *p)//_mm_load_ps一样的加载,但是不要求地址是16字节对齐

set系列,用于加载数据,类似于load操作,但是大部分需要多条指令完成,可能不需要16字节对齐

__m128 _mm_set_ss(float w)//对应于_mm_load_ss的功能,不需要字节对齐,需要多条指令(r0 = w, r1 = r2 = r3 = 0.0)

__m128 _mm_set_ps(float z, float y, float x, float w)//对应于_mm_load_ps的功能,参数是四个单独的单精度浮点数,所以也不需要字节对齐,需要多条指令。(r0=w, r1 = x, r2 = y, r3 = z,注意顺序)

__m128 _mm_set1_ps(float w)//对应于_mm_load1_ps的功能,不需要字节对齐,需要多条指令。(r0 = r1 = r2 = r3 = w)

__m128 _mm_setr_ps(float z, float y, float x, float w)//对应于_mm_loadr_ps功能,不需要字节对齐,需要多条指令。(r0=z, r1 = y, r2 = x, r3 = w,注意顺序)

__m128 _mm_setzero_ps()//清0操作,只需要一条指令。(r0 = r1 = r2 = r3 = 0.0)

store系列,将计算结果等SSE暂存器的数据保存到内存中,与load系列函数的功能对应,基本上都是一个反向的过程。

void _mm_store_ss(float *p, __m128 a)  //一条指令,*p = a0
void _mm_store_ps(float *p, __m128 a)  //一条指令,p[i] = a[i]
void _mm_store1_ps(float *p, __m128 a) //多条指令,p[i] = a0
void _mm_storeh_pi(__m64 *p, __m128 a) //
void _mm_storel_pi(__m64 *p, __m128 a) //
void _mm_storer_ps(float *p, __m128 a) //反向,多条指令
void _mm_storeu_ps(float *p, __m128 a) //一条指令,p[i] = a[i],不要求16字节对齐
void _mm_stream_ps(float *p, __m128 a) //直接写入内存,不改变cache的数据

算数指令系列,SSE提供了大量的浮点运算指令,包括加法、减法、乘法、除法、开方、最大值、最小值等等


__m128 _mm_add_ss (__m128 a, __m128 b)

__m128 _mm_add_ps (__m128 a, __m128 b)

数据类型转换系列

__mm_cvtss_si32   //单精度浮点数转换为有符号32位整数
__mm_cvttss_si32  //单精度浮点数转换为有符号32位整数(带截断操作)
__mm_cvtpi16_ps   //16位有符号整数转换为单精度浮点数

对第一个指令进行详细说明,如下图所示:

  • SSE应用实例

一般而言,使用SSE指令写代码,步骤如下:

  1. 使用load/set函数将数据从内存加载到SSE暂存器;
  2. 使用相关SSE指令完成计算等;
  3. 使用store系列函数将结果从暂存器保存到内存,供后面使用。

例一:使用SSE指令完成加法运算(不要求字节对齐)

#include <emmintrin.h>
#include <iostream>

using namespace std;

int main(int argc, char* argv[]){
	float op1[4] = { 1.0, 2.0, 3.0, 4.0 };
	float op2[4] = { 1.0, 2.0, 3.0, 4.0 };
	float result[4];

	__m128  a;
	__m128  b;
	__m128  c;

	// Load
	a = _mm_loadu_ps(op1);
	b = _mm_loadu_ps(op2);

	// Calculate
	c = _mm_add_ps(a, b);	// c = a + b

	// Store
	_mm_storeu_ps(result, c);

	cout << result[0] << endl;
	cout << result[1] << endl;
	cout << result[2] << endl;
	cout << result[3] << endl;
	system("pause");

	return 0;
}

例二:使用SSE指令完成加法运算(要求字节对齐)


#include <emmintrin.h>
#include <iostream>

using namespace std;

int main(int argc, char* argv[]){

	__declspec(align(16)) float op1[4] = { 1.0, 2.0, 3.0, 4.0 };	
	__declspec(align(16)) float op2[4] = { 1.0, 2.0, 3.0, 4.0 };	
	_MM_ALIGN16 float result[4];		// _MM_ALIGN16等同于__declspec(align(16))

	__m128  a;
	__m128  b;
	__m128  c;

	// Load
	a = _mm_load_ps(op1);
	b = _mm_load_ps(op2);

	// Calculate
	c = _mm_add_ps(a, b);	// c = a + b

	// Store
	_mm_store_ps(result, c);

	cout << result[0] << endl;
	cout << result[1] << endl;
	cout << result[2] << endl;
	cout << result[3] << endl;
	system("pause");

	return 0;
}

例三:使用SSE指令完成多个数据的加法运算(要求字节对齐)

如果想使用SSE计算一个浮点型数组中每个元素的平方根,我们不必去声明__m128类型的数组,可以直接将你的数组强制类型转换成__m128*,然后使用SSE的命令操作这个数组。

	__declspec(align(16)) float array[]  = { 1.0,  2.0, 3.0, 4.0 };
	__m128* ptr = (__m128*)array;
	__m128 t = _mm_sqrt_ps(*ptr);

#include <emmintrin.h>
#include <iostream>
#include<Windows.h>

using namespace std;

void sse_add(float *srcA, float *srcB, float *dest, int n){
	int len = n >> 2;
	for (int i = 0; i < len; i++){
		*(__m128*)(dest + i * 4) = _mm_add_ps(*(__m128*)(srcA + i * 4), *(__m128*)(srcB + i * 4));
	}
}

void normal_add(float *srcA, float *srcB, float *dest, int n){
	for (int i = 0; i < n; i++){
		dest[i] = srcA[i] + srcB[i];
	}
}

int main(){

	DWORD timeStart = 0, timeEnd = 0;
	const int size = 10000; //申请的内存中存放的数据个数
	const int count = 10000;//循环计算的次数,便于观察执行效率

	// 分配16字节对齐的内存
	_MM_ALIGN16 float *srcA = (_MM_ALIGN16 float*)_mm_malloc(sizeof(float)*size, 16);
	_MM_ALIGN16 float *srcB = (_MM_ALIGN16 float*)_mm_malloc(sizeof(float)*size, 16);
	_MM_ALIGN16 float *dest = (_MM_ALIGN16 float*)_mm_malloc(sizeof(float)*size, 16);

	// 初始化
	for (int i = 0; i < size; i++){
		srcA[i] = (float)i;
	}
	memcpy_s(srcB, sizeof(float) * size, srcA, sizeof(float) * size);

	// 标准加法
	timeStart = GetTickCount();
	for (int i = 0; i < count; i++){
		normal_add(srcA, srcB, dest, size);
	}
	timeEnd = GetTickCount();
	cout<<"标准加法"<<(timeEnd - timeStart)<<"毫秒"<<endl;

	// SSE指令加法
	timeStart = GetTickCount();
	for (int i = 0; i < count; i++){
		sse_add(srcA, srcB, dest, size);
	}
	timeEnd = GetTickCount();
	cout << "SSE加法" <<(timeEnd - timeStart)<< "毫秒" << endl;

	// 释放内存
	_mm_free(srcA);
	_mm_free(srcB);
	_mm_free(dest);

	system("pause");
	return 0;
}

  • 27
    点赞
  • 140
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
ARMv9指令集手册是ARM体系架构(ARM architecture)的第9版指令集的详细说明文档。ARMv9是ARM架构的最新版本,它引入了一系列新的特性和功能,以提供更高的性能和更好的能效。 ARMv9指令集手册通过详细描述ARMv9架构的指令结构、编码方式和操作规则,让软件开发者和硬件设计者能够深入了解ARMv9体系架构,从而更好地利用ARMv9架构提供的新特性。 ARMv9指令集手册通常包括以下内容: 1. 简介:介绍ARMv9指令集的发展背景、整体结构和基本术语。 2. 指令集概述:对ARMv9指令集的整体设计思想、特点和新功能进行概述。 3. 指令编码:详细解释每一条指令的编码方式,包括操作码、寄存器操作数、立即数、地址计算等。 4. 指令格式:描述不同指令格式的定义和用法,例如数据处理指令、分支跳转指令、加载存储指令等。 5. 寄存器和内存管理:介绍ARMv9架构中的寄存器和内存管理机制,包括寄存器文件、特殊寄存器、地址映射等。 6. 异常和中断处理:说明ARMv9架构支持的异常和中断处理机制,以及相应指令的使用方法。 7. 特殊指令和特权指令:介绍一些特殊用途的指令和特权指令,如访问控制指令、系统调用指令等。 8. 协处理器:描述与ARMv9架构相关的协处理器的设计和使用方法。 9. 实例和示例代码:提供一些实例和示例代码,以帮助开发者更好地理解和使用ARMv9指令集。 通过仔细阅读ARMv9指令集手册,开发者能够更好地理解ARMv9架构的特性和功能,并能够开发出更高效、优化的ARMv9架构的应用程序。此外,硬件设计者也可以根据指令集手册提供的设计规范,设计出兼容ARMv9架构的处理器、芯片组和系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的路飞桑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值