数据库浅谈之向量化
HELLO,各位博友好,我是阿呆 🙈🙈🙈
这里是数据库浅谈系列,收录在专栏 DATABASE 中 😜😜😜
本系列阿呆将记录一些数据库领域相关的知识 🏃🏃🏃
OK,兄弟们,废话不多直接开冲 🌞🌞🌞
一 🏠 概述
SIMD 简介
计算机程序需要编译成指令才能让 CPU 识别并执行运算。所以,CPU 指令处理数据的能力是衡量 CPU 性能的重要指标。
为了提高 CPU 指令处理数据的能力,半导体厂商在 CPU 中推出了一些可以同时并行处理多个数据的指令 —— SIMD 指令
SIMD 的全称是 Single Instruction Multiple Data,中文名“单指令多数据”。顾名思义,一条指令处理多个数据。
如下图所示:
- 一个普通加法指令,一次只能对两个数执行一个加法操作
- 一个 SIMD 加法指令,一次可以对两个数组(向量)执行加法操作
SSE 指令下:矩阵运算主流程是 :加载 -> 运算 -> 存储
- 加载:将数据从内存加载到专用寄存器,一次可以加载多个数据
- 运算:使用 SSE 乘/加命令将 积 和 累加
- 存储:将结果保存到内存中
具体命令可参考 Intel SSE指令网址
SIMD基础知识
SIMD是单指令多数据技术,目前 Intel 处理器支持的 SIMD 技术包括 MMX、SSE 以及 AVX
SIMD的思想在不同平台架构下进行了实现
- X86:SSE 指令
- ARM:NEON指令
- MIPS:MSA
不同平台架构下为实现SIMD思想也添加了不同的硬件支持(专用寄存器):
- X86:目前支持 128bit 寄存器,256bit,甚至是 512bit
- ARM:128bit
- MIPS:128bit
SIMD 优点
在对大量数据执行相同操作时,使用一个指令完成,能取得巨大性能提升
二 🏠 核心
SIMD使用
在 C 程序中使用 SIMD 指令有两种方案,一 是使用内联汇编,二 是使用 Intel 提供的封装函数库
简单的数组相乘:
1. 内联汇编
#include <stdio.h>
#include <stdlib.h>
int main()
{
float a[4] = { 1,2,3,4 };
float b[4] = { 5,6,7,8 };
float res[4];
__asm__ __volatile__(
"movups %1,%%xmm0\n\t" // 将a所指内存的128位数据放入xmm0寄存器
"movups %2,%%xmm1\n\t" // 将b所指内存的128位数据放入xmm0寄存器
"mulps %%xmm1,%%xmm0\n\t" // 计算 xmm0 * xmm1,结果放入 xmm0
"movups %%xmm0,%0\n\t" // 将xmm0的数据放入res所指内存
:"=m"(res) //output
:"m"(a),"m"(b) //input
);
for(int i=0;i<4;i++)
printf("%.2f\n",res[i]);
return 0;
}
先看 movups 指令,分为四个部分:
- mov,表示数据移动
- u,表示 unaligned,内存未对齐。a,表示 aligned,内存已对齐
- p,表示 packed,打包数据,对128位所有数据执行操作。如果是s,则表示 scalar,标量数据,仅对 128 位内第一个数执行操作
- s表示 single precision floating point,将数据视为32位单精度浮点数,一组4个。d,表示 double precision floating point,将数据视为64位双精度浮点,一组两个
从内存中向寄存器加载数据时,必须区分数据是否对齐。SSE指令要求数据按16字节对齐,未对齐数据必须使用movups,已对齐数据可以任意使用movups或者movaps
2. 指令向量化
sse : m128 _mm_add_epi32(m128 a , __m128 b )
#include <stdio.h>
#include <stdlib.h>
#include <emmintrin.h>
int main()
{
float a[4] = { 1,2,3,4 };
float b[4] = { 5,6,7,8 };
float res[4];
__m128 A = _mm_loadu_ps(a);
__m128 B = _mm_loadu_ps(b);
__m128 RES = _mm_mul_ps(A, B);
_mm_storeu_ps(res, RES);
for(int i=0;i<4;i++)
printf("%.2f\n",res[i]);
return 0;
}
- _mm,表示这是一个64位/128位的指令,_mm256 和 _mm512 则表示是 256 位或是 512 位的指令
- _loadu,表示unaligen的load指令,不带u后缀的为 aligen
- _ps,同上面汇编指令
3. 自动向量化
SSE : 使用 -O3 即可支持 SSE 自动向量化(auto vectorization)
使用 GCC 可以通过 -S 参数,输出中间汇编文件,可检查是否自动将代码进行向量化
- 以 v 开头的指令,如 vpmulld、vpaddd、vmovdqu 都是向量化指令
- xmm、ymm、zmm 分别表示 128 位、256 位 和 512 位的向量化使用的寄存器
- 本例中使用了 -mavx512f 编译选项要求编译器使用 AVX512,所以汇编代码使用的寄存器是 512 位的 zmm 系列
- 本例中将数组 x 的长度固定为 64 个 int(512 位的倍数),是为了让实例生成的汇编代码更加简洁,不是向量化的强制要求
编译:g++ -o built-vec -mavx512f built-vec.cc
-mavx512f 表示使用 AVX512 指令集。如果 CPU 不支持 AVX512,可以使用其它 SIMD 指令集。相关编译选项有:-mmmx、-msse、-msse2、-msse3、-mssse3、-msse4.1、-msse4.2、-msse4、-mavx、-mavx2、-mavx512f、-mavx512pf、-mavx512er、-mavx512cd、-mavx512vl、-mavx512bw、-mavx512dq、-mavx512ifma、-mavx512vbmi
三 🏠 结语
身处于这个浮躁的社会,却有耐心看到这里,你一定是个很厉害的人吧 👍👍👍
各位博友觉得文章有帮助的话,别忘了点赞 + 关注哦,你们的鼓励就是我最大的动力
博主还会不断更新更优质的内容,加油吧!技术人! 💪💪💪