当前位置:主页 > 查看内容

SIMD

发布时间:2021-06-08 00:00| 位朋友查看

简介:初始SIMD 前言 AVX基础介绍 矩阵乘加速 总结 前言 完成了计算机系统原理之后来做一个总结本次实验时继多线程之后继续研究矩阵乘优化的内容主要使用SIMD指令集并与上次实验的多线程进行对比。 SIMD即单指令集多数据Single Instruction Multiple Data。说的简……

前言

完成了计算机系统原理之后来做一个总结,本次实验时继多线程之后继续研究矩阵乘优化的内容,主要使用SIMD指令集并与上次实验的多线程进行对比。

SIMD即单指令集多数据(Single Instruction Multiple Data)。说的简单一点就是一种向量运算,想象一下在单指令单数据上你要做一个op需要几步?你得先取指译码,然后访问内存获得操作数,然后才能进行计算吧,Intel告诉我们可以用SIMD一下子完成128bit,或者256bit,甚至是512bit数据的运算,怎么理解这样的一个数据结构呢,可以把它想象成一个可以同时按位计算的数组,既然是个数组说明也可以通过索引访问数据,这里有个误区是这个数据结构本身表现出来的数没有太多意义,如一个__m128 a存放了4个float类型的数,那么a就是一个类似数组的东西。

AVX基础介绍

MMX和SSE用的不多这里就以AVX为例了如果想看完整的更深层的建议看Intel官网https://software.intel.com/content/www/us/en/develop/home.html

数据类型

数据类型描述
__m128包含四个float类型数字的向量
__m128d包含两个double类型数字的向量
__m128i包含若干个int类型数字的向量
__m256包含八个float类型数字的向量
__m256d包含四个double类型数字的向量
__m256i包含若干个int类型数字的向量

访存操作

数据类型描述
_mm256_load_ps/pd从对齐的内存地址加载浮点向量
_mm256_load_si256从对齐的内存地址加载整形向量
_mm256_loadu_ps/pd从未对齐的内存地址加载浮点向量
_mm256_loadu_si256从未对齐的内存地址加载整形向量

写入的操作把load换成store即可,这里需要知道SIMD是要求对齐的,可以用align对齐,当然你也可以用上述的不对齐时的操作写代码,但是据我测试似乎时间花费更多。

加减乘

数据类型描述
_mm256_add_ps/pd对两个浮点向量做加法
_mm256_sub_ps/pd对两个浮点向量做减法
_mm256_mul_ps/pd对两个浮点向量做乘法
_mm256_hadd_ps/pd水平方向对两个浮点向量做加法
_mm256_hsud_ps/pd垂直方向对两个浮点向量做加法

这里解释一下水平方向时怎么操作的,如下图。
在这里插入图片描述

矩阵乘加速

没错又是我们的老朋友矩阵乘,有了上述的基础知识已经完全可以写出一个入门级的SIMD优化代码了。

int main() {
	double* A, * B, * C;
	A = new double[M * N];
	B = new double[N * P];
	C = new double[M * P];
	for (int i = 0; i < M; i++) {
		for (int j = 0; j < N; j++) {
		A[i * N + j] = i * N + j;
		}
	}
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < P; j++) {
		B[i * P + j] = i * P + j;
		}
	}
avxmul(A, B, C); //avx+转置
return 0;
}

//avx+转置
void avxmul(double* A, double* B, double* C) {
	for (int i = 0; i < M; i++) {
	int c = i * P;
		for (int j = 0; j < P; j++) {
		C[c + j] = 0;
		}
	}
	clock_t start, end;
	int i, j, k;
	double temp;
	start = clock();
	double* B1 = T(B);
	for (i = 0; i < M; i++) {
		for (j = 0; j < P; j++) {
		temp = 0; double t[4] = { 0 };
		int a = i * N; int b = j * N;
			for (k = 0; k < N && k + 4 <= N; k += 4) {
			_mm256_store_pd(&t[0],
			_mm256_mul_pd(_mm256_load_pd(&A[a + k]), _mm256_load_pd(&B1[b + k])));
			temp += t[0] + t[1] + t[2] + t[3];
			}
			C[i * P + j] = temp;
		}
	}
end = clock();
cout << "avx+转置时间: " << (double)(end - start) / CLOCKS_PER_SEC
* 1000 << "ms" << endl;
}

总结

本次实验我们以 SIMD(单指令多数据)为主要研究对象,实现了矢量加和矩阵乘的优化。在实验过程中我们不断在探讨 SIMD 与 SIMT(单指令多线程)计算机系统-SIMD 程序设计与性能分析之间的区别与联系。

从单个线程看,假设我们用 SSE 正在处理一个四维向量(指令向量化处理是 SIMD 的核心),这条指令最快在一个 cycle 就能完成,但是在SIMT 架构中则需要把这个四维向量分解开,一个 cycle 处理一个数据,最快也需要 4 个 cycle,但这也需要比起 SIMT 四倍的逻辑单元。换而言之在同等算力进行对比下,其实 SIMD 可以拿出更少的线程个数来实现 SIMT 的算力,这也是为什么在我们的实验中 SIMD 往往优化程度更好的原因。

但是当我们增加线程数的时候,SIMD 的优势将会逐渐减少,而倘若 SIMD 单纯把向量维度扩大时,浪费也会增加,所以现在一些架构会对 16 个线程分成四组的 SIMD 解决这个问题,当然不管是 SIMD 还是 SIMT 都存在不少的局限,SIMD 需要严格按照维数的整数倍去实现,否则会造成逻辑单元的浪费,而 SIMT 则需要在计算机的可以使用的逻辑单元数内实现线程数的创建,否则线程数的增加将不能导致速度加快,反而需要大量的分支跳转影响了效率,这里也体现了算法设计上面的折衷思想,这种折衷既与算法本身有关,又与对应的硬件有关。

经过上面的陈述仿佛 SIMD 和SIMT 存在不同程度的对立,但是这并不意味着 SIMD 就不能与 SIMT 进行“合作优化”,我们设计了同时使用 SIMD 与 SIMT 的算法,算法思路也很简单,只需要思考在多线程下每个线程是否支持 SIMD 即可。

;原文链接:https://blog.csdn.net/weixin_50281113/article/details/115564074
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐