浮点指令
程序员文章站
2022-07-02 23:23:21
...
第一代:x87浮点指令集
特征
- 使用80位浮点协处理器处理浮点运算
- 浮点协处理器内部为栈结构
运算过程
指令
- 这里只介绍部分指令,详细的参考Intel / AMD 开发手册
;入栈
fld st(i) ;将st(i)的值压入栈顶
fld mem32/mem64/mem80 ;将浮点数压栈 交给st(0) 同时原st(0)交给st(1)
;加法
fadd st(0),st(i) ;st(0)和st(i)作加法 结果放在st(0)
fadd st(i),st(0) ;st(i)和st(0)作加法 结果放在st(0)
fadd mem32/mem64 ;st(0)与32或64位地址内存上的值相加,结果放在st(0)
faddp ;st(1)和st(0)相加,结果放在st(1),并弹出栈顶值
faddp st(i),st(0) ;st(i)和st(0)相加,结果放在st(i),并弹出栈顶值
fiadd mem32/mem64 ;st(0)与16位或32位的整型值相加,结果放在st(0)
;出栈
fst st(i) ;将st(0)值拷贝到st(i)
fst mem32/mem64 ;将st(0)值拷贝到指定内存地址处,不改变栈顶
fstp st(i) ;将st(0)值拷贝到st(i),同时弹出栈顶
fstp mem32/mem64 ;将st(0)值拷贝到指定内存地址处,同时弹出栈顶
- 代码示例
int main()
{
double f1 = 1.0f;
double f2 = 2.0f;
float ret;
__asm
{
fld f1;
fld f2;
faddp st(1), st(0); //加完值放在栈顶
fstp ret; //将栈顶值弹出到指定内存中
}
return 0;
}
第二代:多媒体指令集(MMX)
特征
1.使用64位浮点协处理器处理浮点运算
2.寄存器可以拆分后做同时运算,极大提升了运算效率
- 可以拆分为8个单字节同时作运算
- 可以拆分为2个float同时作运算
3.不再使用栈结构,对比x87性能得到极大提升(差不多在10倍左右)
4.寄存器在拆分做独立运算时有两种模式
- 环绕模式: 255+1 = 0 (溢出)
- 饱和模式: 255+1 = 255 (不溢出)
指令
- 汇编语法,这里只介绍部分语法,详细的参考Intel / AMD 开发手册
int main()
{
char ary1[] = { 255, 2, 3, 4, 5, 6, 7, 8 };
char ary2[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
char ary3[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
__asm
{
movq MM0, ary1 //相当于mov指令,64位(8字节)传送
movq MM1, ary2
paddb MM0, MM1 //环绕模式加法,允许溢出
paddusb MM0, MM1 //饱和模式加法,不允许溢出
movq ary3, MM0
}
return 0
- 高级语法
#include <iostream>
#include <time.h>
#include <mmintrin.h> //MMX指令集对应的头文件
void x87_add(char *ary, int size)
{
for (int i = 0; i < size; i++)
ary[i] += 1;
}
void mmx_add(char *ary, int size)
{
__m64 sum;//64位联合体
sum.m64_i64 = 0x0101010101010101L;
__m64 *p = (__m64 *)ary;
for (int i = 0; i < size; i++)
_m_paddb(p[i], sum); //打包加法,相当于每次给 8个字节的char同时加 1
}
int main()
{
//x87
int size = 100000000;
char* ary = (char*)malloc(sizeof(char) * size);
for (int i = 0; i < size; i++)
ary[i] = 1;
time_t start = clock();
x87_add(ary, size);
printf("x87_add time:%d\n", clock() - start);
start = clock();
mmx_add(ary, size / 8);
printf("mmx_add time:%d\n", clock() - start);
free(ary);
return 0;
}
第三代:多媒体指令集(SSE)
特征
1.采用128位位浮点协处理器处理浮点运算
2.寄存器可以拆分后做同时运算,极大提升了运算效率,广泛应用于3D游戏开发
- 可以拆分为16个单字节同时作运算
- 可以拆分为4个float同时作运算
- SSE2.0以后支持拆分为2个double同时作运算
指令
- 汇编语法(这里只介绍部分语法,详细的参考Intel / AMD 开发手册)
int main()
{
char ary1[] = { 255, 2, 3, 4, 5, 6, 7, 8, 255, 2, 3, 4, 5, 6, 7, 8 };
char ary2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };
char ary3[] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };
__asm
{
movdqu xmm0, ary1 //相当于mov指令,这里mov的是128位(16字节),movdqu(移动非对齐的双四字),movdqa(移动对齐的双四字)
movdqu xmm1, ary2
paddq xmm0, xmm1 //环绕模式加法,允许溢出
//paddusb xmm0, xmm1 //饱和模式加法,不允许溢出
movdqu ary3, xmm0
}
return 0;
}
- 高级语法
#include <iostream>
#include <time.h>
#include <xmmintrin.h> //SSE float+float int+int
#include <emmintrin.h> //SSE2
#include <intrin.h> //all
float x87_add(float *ary, int size)
{
float sum = 0.0f;
for (int i = 0; i < size; i++)
sum += ary[i];
return sum;
}
float sse_add(float *ary, int size)
{
__m128 sum = { 0.0f, 0.0f, 0.0f, 0.0f };//128位联合体
__m128 *p = (__m128*)ary;
for (int i = 0; i < size; i++)
sum = _mm_add_ps(sum, p[i]); //打包加法,相当于每次给4个浮点作运算
return sum.m128_f32[0] + sum.m128_f32[1] + sum.m128_f32[2] + sum.m128_f32[3];
}
int main()
{
//SSE: MOVSS ADDSS ADDSD ADDPS ADDPD
int size = 200000000;
float sum;
float* ary = (float*)malloc(sizeof(float) * size);
for (int i = 0; i < size; i++)
ary[i] = 1.0f;
time_t start = clock();
sum = x87_add(ary, size);
printf("x87_add sum:%f time:%d\n", sum, clock() - start);
start = clock();
sum = sse_add(ary, size / 4);
printf("sse_add sum:%f time:%d\n", sum, clock() - start);
free(ary);
return 0;
}
第四代:AVS浮点指令集
特征
1.采用256位位浮点协处理器处理浮点运算
2.寄存器可以拆分后做同时运算,极大提升了运算效率,广泛应用于3D游戏开发
- 可以拆分为32个单字节同时作运算
- 可以拆分为8个float同时作运算
- SSE2.0以后支持拆分为4个double同时作运算
3.对比SSE指令集,做8个float同时运算时效率提升2倍(但实际提升不大,因为3D游戏坐标最多只需3个float同时作运算即可)
指令
- 汇编语法(这里只介绍部分语法,详细的参考Intel / AMD 开发手册) ,相对于SSE的语法,前缀加V即可
int main()
{
char ary1[] = { 255, 2, 3, 4, 5, 6, 7, 8, 255, 2, 3, 4, 5, 6, 7, 8 };
char ary2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };
char ary3[] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };
__asm
{
vmovdqu xmm0, ary1 //相当于mov指令,这里mov的是128字节
//vmovdqu ymm0, ary1 //相当于mov指令,这里寄存器是ymm0,对应mov的是256字节
vmovdqu xmm1, ary2
vpaddq xmm0, xmm1 //环绕模式加法,允许溢出
//vpaddusb xmm0, xmm1 //饱和模式加法,不允许溢出
vmovdqu ary3, xmm0
}
return 0;
}
- 高级语法
#include <iostream>
#include <time.h>
#include <intrin.h> //all
#include <immintrin.h> //avx
//版本迭代:SSE SSE1 SSE2 SSE3 SSE4 SSE4.1 SSE4.2 SSE5.0(AVX) AVX2
float x87_add(float *ary, int size)
{
float sum = 0.0f;
for (int i = 0; i < size; i++)
sum += ary[i];
return sum;
}
float avx_add(float *ary, int size)
{
__m256 sum = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
__m256 *p = (__m256*)ary;
for (int i = 0; i < size; i++)
sum = _mm256_add_ps(sum, p[i]); //一次可同时对8个float作加法 性能极大提升
return sum.m256_f32[0];
}
int main()
{
//AVX: VMOVSS VADDSS .....
int size = 200000000;
float sum;
float* ary = (float*)malloc(sizeof(float) * size);
for (int i = 0; i < size; i++)
ary[i] = 1.0f;
time_t start = clock();
sum = x87_add(ary, size);
printf("x87_add sum:%f time:%d\n", sum, clock() - start);
start = clock();
sum = avx_add(ary, size / 8);
printf("avx_add sum:%f time:%d\n", sum, clock() - start);
free(ary);
return 0;
}
vs中设置默认浮点指令集位置
上一篇: npm