AVX指令集加速矩阵乘法
AVX简介
SIMD
SIMD(Single Instruction Multiple Data,单指令多数据流),是一种实现空间上的并行性的技术。这种技术使用一个控制器控制多个处理单元,同时对一组数据中的每一个数据执行相同的操作。在 SIMD 指令执行期间,任意时刻都只有一个进程在运行,即 SIMD 没有并发性,仅仅只是同时进行计算。
在 Intel 的 x86 微架构处理器中,SIMD 指令集有 MMX、SSE、SSE2、SSE3、SSSE3、SSE4.1、SSE4.2、AVX、AVX2、AVX512。
AVX
AVX 是 SSE 架构的延伸,将 SSE 的 XMM 128bit 寄存器升级成了 YMM 256bit 寄存器,同时浮点运算命令扩展至 256 位,运算效率提升了一倍。另外,AVX 还添加了三操作数指令,以减少在编码时先复制再运算的动作。
AVX2 将大多数整数运算命令扩展至 256 位,同时支持 FMA(Fused Multiply-Accumulate,融合乘法累加)运算,可以在提高运算效率的同时减少运算时的精度损失。
AVX512 将 AVX 指令进一步扩展至 512 位。
AVX指令介绍
参考该网站:Crunching Numbers with AVX and AVX2 - CodeProject
基于AVX的矩阵乘法
算法思想
结合上图,首先需要将矩阵 C 初始化为 0 矩阵。为了充分利用 AVX 单指令多数据的计算优势,需要以向量为基本运算单元,将矩阵 A 第 i 行第 j 列的元素与矩阵 B 第 j 行的向量相乘,结果向量与矩阵 C 第 i 行的向量累加。当矩阵 A 第 i 行的所有元素均与矩阵 B 每一行的向量相乘并累加至矩阵 C 第 i 行的向量后,矩阵 C 第 i 行的计算结束。
假设矩阵的元素为 float 类型(32位)的浮点数,由于 AVX 采用 256 位寄存器存储向量,所以一个向量可以存储 8 个元素。当矩阵的列数不为 8 的整数倍时,即一行的元素个数不为 8 的整数倍时,最后一个向量的后几位需要设置为 0。若采用兼容未对齐数据的方法则需要解决内存冲突的问题。
代码
在具体实现时,矩阵 A 第 i 行第 j 列的数据 A[i][j] 会被复制成长度为 8 的向量 vecA,矩阵 B 每一行的向量 vecB 则从指定内存地址读入。由于矩阵的列数可以不是 8 的整数倍(对于元素为float类型的矩阵),所以数据可以是未对齐的,因此采用 loadu 从内存装入矩阵数据。
对于向量的相乘和累加,因为两个 n 位数字相乘的结果最大可以占 2n 位,因此当两个 float 类型的浮点数 a 与 b 相乘时,其结果是最接近 a*b 的 float 数值 round(a*b)。在本算法中,两个浮点数相乘后还要累加到 C 矩阵中,相比于普通的相乘后累加 round(a*b)+c,FMA运算可以实现 round(a*b+c)。因此这种方式拥有更高的精确性。
这里我并没有将最后一个向量的后几位设置为 0,所以存在一个问题:假如此时矩阵 B 为 7*6,当 loadu 读入矩阵 B 第二行的数据时,第二行只有 6 个 float 类型的 32 位浮点数,不足以构成 256 位的向量,因此指令会继续读入第三行的前两个数,计算完成后,第三行的前两个数也被保存至矩阵 C。接着,指令读入第三行和第四行前两个数,计算完成后保存。不难发现,从第二行开始到最后一行,每行的前两个数都被计算了两次。并且对于第一次计算来说,后两个数的计算过程不符合算法的原理,是错误数据。解决方法很简单,在矩阵 C 各行开始计算之前,重新初始化该行的元素值为 0,以消除第一次错误计算的影响。
#include <immintrin.h>
|
|
#include <stdio.h>
|
|
#include <pthread.h>
|
|
#include <time.h>
|
|
#define MATRIX_SIZE 8192
|
|
#define NUM_THREAD 4
|
|
float matA[MATRIX_SIZE][MATRIX_SIZE];
|
|
float matB[MATRIX_SIZE][MATRIX_SIZE];
|
|
float matC[MATRIX_SIZE][MATRIX_SIZE];
|
|
int step = 0;
|
|
void* multiplicationAVX(void* args) {
|
|
__m256 vecA, vecB, vecC;
|
|
int thread = step++;
|
|
for (int i = thread * MATRIX_SIZE / NUM_THREAD;
|
|
i < (thread + 1) * MATRIX_SIZE / NUM_THREAD; i++) {
|
|
for (int j = 0; j < MATRIX_SIZE; j++) {
|
|
matC[i][j] = 0.0f;
|
|
}
|
|
for (int j = 0; j < MATRIX_SIZE; j++) {
|
|
vecA = _mm256_set1_ps(matA[i][j]);
|
|
for (int k = 0; k < MATRIX_SIZE; k += 8) {
|
|
vecB = _mm256_loadu_ps(&matB[j][k]);
|
|
vecC = _mm256_loadu_ps(&matC[i][k]);
|
|
vecC = _mm256_fmadd_ps(vecA, vecB, vecC);
|
|
_mm256_storeu_ps(&matC[i][k], vecC);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
void* multiplicationNormal(void* args) {
|
|
int thread = step++;
|
|
for (int i = thread * MATRIX_SIZE / NUM_THREAD;
|
|
i < (thread + 1) * MATRIX_SIZE / NUM_THREAD; i++) {
|
|
for (int j = 0; j < MATRIX_SIZE; j++) {
|
|
for (int k = 0; k < MATRIX_SIZE; k++) {
|
|
matC[i][j] += matA[i][k] * matB[k][j];
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
void createMatrix() {
|
|
for (int i = 0; i < MATRIX_SIZE; i++) {
|
|
for (int j = 0; j < MATRIX_SIZE; j++) {
|
|
matA[i][j] = i + j * 2;
|
|
matB[i][j] = i * 2 + j;
|
|
matC[i][j] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
void printMatrix() {
|
|
if (MATRIX_SIZE <= 16) {
|
|
printf("Matriz A");
|
|
for (int i = 0; i < MATRIX_SIZE; i++) {
|
|
printf("\n");
|
|
for (int j = 0; j < MATRIX_SIZE; j++) {
|
|
printf("%f ", matA[i][j]);
|
|
}
|
|
}
|
|
printf("\n\n");
|
|
printf("Matriz B");
|
|
for (int i = 0; i < MATRIX_SIZE; i++) {
|
|
printf("\n");
|
|
for (int j = 0; j < MATRIX_SIZE; j++) {
|
|
printf("%f ", matB[i][j]);
|
|
}
|
|
}
|
|
printf("\n\n");
|
|
printf("Multiplying matrix A with B");
|
|
for (int i = 0; i < MATRIX_SIZE; i++) {
|
|
printf("\n");
|
|
for (int j = 0; j < MATRIX_SIZE; j++) {
|
|
printf("%f ", matC[i][j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
int main() {
|
|
pthread_t threads[NUM_THREAD];
|
|
clock_t start, end;
|
|
createMatrix();
|
|
start = clock();
|
|
for (int i = 0; i < NUM_THREAD; i++) {
|
|
// pthread_create(&threads[i], NULL, multiplicationNormal, NULL);
|
|
pthread_create(&threads[i], NULL, multiplicationAVX, NULL);
|
|
}
|
|
for (int i = 0; i < NUM_THREAD; i++) {
|
|
pthread_join(threads[i], NULL);
|
|
}
|
|
end = clock();
|
|
printMatrix();
|
|
printf("\n\n使用的线程数 -> %d\n", NUM_THREAD);
|
|
printf("\n矩阵大小 -> %d\n", MATRIX_SIZE);
|
|
printf("\n程序运行时间(毫秒) -> %f\n\n", (float)(end - start) * 1000 / CLOCKS_PER_SEC);
|
|
}
|
版权声明:本文由 iiYK 原创,允许转载,转载时请注明文章出处 AVX指令集加速矩阵乘法 – iiYK
AVX指令集加速矩阵乘法相关推荐
- 【27】SIMD:如何加速矩阵乘法?
[计算机组成原理]学习笔记--总目录 [27]SIMD:如何加速矩阵乘法? 引言 一.超线程:Intel 多卖给你的那一倍 CPU 1.背景 2.超线程(Hyper-Threading)技术 二.SI ...
- AVX指令集实现矩阵乘
本节矩阵乘选择方阵 思想:c语言默认按行优先存储,矩阵a * b,a的行连续,可以连续访存,大大提高效率:但是b要按列取数,所以去b的列向量浪费时间,解决办法是:将b转置存储,这样b就可以按行进行连续 ...
- X86 SSE/AVX指令集加速学习
在看了刘文志的<并行编程方法与优化实践>后决定写一写书中的例子或者实际工程中用到加速的一些sample,这本书的pdf我也有,可以在下面留言,我发给你. 1. 使用SSE指令实现了一些简单 ...
- CUDA实例——加速矩阵乘法
Ref: CUDA C programing guide https://docs.nvidia.com/cuda/cuda-runtime-api/index.html 一 什么是CUDA? CUD ...
- CUDA加速计算矩阵乘法进阶玩法(共享内存)
CUDA加速计算矩阵乘法&进阶玩法~共享内存 一.基础版矩阵乘法 二.为什么可以利用共享内存加速矩阵乘法 1.CUDA内存读写速度比较 2.申请共享内存 三.改进版矩阵乘法(利用共享内存) 一 ...
- CUDA实例系列一: 矩阵乘法优化
CUDA实例系列一----矩阵乘法优化 很多朋友在学习CUDA的时候都会面临一个题目----矩阵乘法, 这也是CUDA最广泛的应用之一. 本文将详细讲解如何利用GPU加速矩阵乘法的计算. 话不多说, ...
- ncnn 框架分析 openmp多核加速 缓存 仿存 cache 快速矩阵乘法 单指令多数据指令SIMD
ncnn 框架分析 本文github链接 博文末尾支持二维码赞赏哦 _ 在ncnn中建立新层 ncnn 下载编译使用 参考1 参考2 1. param 和 bin 文件分析 param 7767517 ...
- c++的矩阵乘法加速trick
c++的矩阵乘法加速trick 最近读RNNLM的源代码,发现其实现矩阵乘法时使用了一个trick,这里描述一下这个trick. 首先是正常版的矩阵乘法(其实是矩阵乘向量) void matrixXv ...
- 基于PYNQ-Z2开发板实现矩阵乘法加速详细流程
基于PYNQ-Z2开发板实现矩阵乘法加速 主要内容 1.在Vivado HLS中生成矩阵乘法加速的IP核. 2.在Vivado中完成Block Design. 3.在Jupyter Notebook上 ...
最新文章
- HDU 2444 The Accomodation of Students
- Java学习—— for循环
- 【css】报错,错误代码77,CURLE_SSL_CACERT_BADFILE (77)解决方法
- docker always_介绍两款Docker可视化工具
- stm32串口传输数据第一个数据被吞_stm32串口发送数据复位 第一个数据丢失
- 曹大带我学 Go(12)—— 面向火焰图编程
- 静态时序分析——基础概念
- 【SLAM】安装 g2o_viewer
- 前端学习(872):注册事件兼容性处理
- (JAVA)IO流之读写单个字节和复制文本文件
- find函数常见错误_终于找到你,查找函数,find必不可少
- 【elasticsearch】 elasticsearch document 路由 (routing) 到shard
- V-rep学习笔记:机器人逆运动学数值解法(Cyclic Coordinate Descent Method)
- python 组合优化 回撤最小_【策略回测】多因子搭配组合优化(内附bonus)
- delphi 软件在线人数统计_【大学分析】计算机爆满,软件爆冷!这所985大学考研分数截然不同!...
- Python进阶(十一)装饰器
- P4213 【模板】杜教筛(杜教筛)题解
- java命令行参数是什么_Java实验课:命令行参数是什么?
- 电视家3.0怎么安装到电视上?常用三种方法介绍
- 15.编写LED程序及反汇编工具