SIMD学习 -- 用SSE2指令作点乘和累加计算
这几天在做学校的一个学习小项目,需要用到SIMD指令计算提速。也是第一次碰这个,看了一些资料和代码,模仿着写了两个函数。
void sse_mul_float(float *A, float *B, int cnt):两段内存float数据点乘,结果覆盖第一组内存。
float sse_acc_float(float *A, int cnt):一组内存float值累加。
注:
1. 没有考虑中间的精确问题,结果会有误差。
2. 每个函数包括指令操作部分和C++语句计算部分。本文附的代码注释介绍指令部分思路。
**3. 关于内存对齐,我不是很懂,所以下面的代码中判断是否对齐的相关语句我写的也不是很正确,所有后面都补上了一点C++的明白操作。
因此,有些指令操作也许没用上。
头文件
- #include "time.h"
- #include "stdafx.h"
- #include<iostream>
- #include <stdlib.h>
- #include <stdio.h>
- #include <tchar.h>
- #include <math.h>
- #include <time.h>
- #include <windows.h>
- #include <iomanip>
- #include <sys/timeb.h>
- using namespace std;
sse_mul_float asm部分
- //MOV EAX,1 ;request CPU feature flags
- //CPUID ;0Fh, 0A2h CPUID instruction
- //TEST EDX,4000000h ;test bit 26 (SSE2)
- //JNZ >L18 ;SSE2 available
- int cnt1;
- int cnt2;
- int cnt3;
- //we process the majority by using SSE instructions
- if (((int)A % ) || ((int)B % )) //如果内存不对齐
- {
- cnt1 = cnt / ; //该loop一轮处理16个float*float
- cnt2 = (cnt - ( * cnt1)) / ; //该loop一轮处理4个float*float
- cnt3 = (cnt - ( * cnt1) - ( * cnt2)); //该loop一轮处理1个float*float
- _asm
- {
- mov edi, A; //先将内存地址放入指针寄存器
- mov esi, B;
- mov ecx, cnt1; //循环寄存器置值
- jecxz ZERO; //如果数据量不超过16个,则跳过L1
- L1:
- //xmm 寄存器有128bit
- //movups XMM,XMM/m128
- //传128bit数据,不必对齐内存16字节.
- movups xmm0, [edi];
- movups xmm1, [edi + ];
- movups xmm2, [edi + ];
- movups xmm3, [edi + ];
- //为什么只载入4*4个float? 到上面看看这一轮需要处理多少数据
- movups xmm4, [esi];
- movups xmm5, [esi + ];
- movups xmm6, [esi + ];
- movups xmm7, [esi + ];
- //mulps XMM,XMM/m128
- //寄存器按双字对齐,
- //共4个单精度浮点数与目的寄存器里的4个对应相乘,
- //结果送入目的寄存器, 内存变量必须对齐内存16字节.
- mulps xmm0, xmm4;
- mulps xmm1, xmm5;
- mulps xmm2, xmm6;
- mulps xmm3, xmm7;
- //(一个float占4字节,也就是32bit)
- //到这里,xmm0-3寄存器里都有了4个float的乘积结果
- //然后回载到相应内存
- movups[edi], xmm0;
- movups[edi + ], xmm1;
- movups[edi + ], xmm2;
- movups[edi + ], xmm3;
- //记得给指针移位
- //64=16 * 4
- //每一轮处理了16次float * float,每一个float占4字节
- //所以移位应该加64
- add edi, ;
- add esi, ;
- loop L1;
- ZERO:
- mov ecx, cnt2;
- jecxz ZERO1;
- L2:
- movups xmm0, [edi]; //对于4个float,一个xmm寄存器正好够用
- movups xmm1, [esi];
- mulps xmm0, xmm1; //对应相乘,结果在xmm0
- movups[edi], xmm0; //由xmm0回载内存
- add edi, ; //指针移位
- add esi, ;
- loop L2;
- ZERO1:
- mov ecx, cnt3;
- jecxz ZERO2;
- mov eax, ;
- L3:
- movd eax, [edi]; //对于单个float * float,无需sse指令
- imul eax, [esi];
- movd[edi], eax;
- add esi, ;
- add edi, ;
- loop L3;
- ZERO2:
- EMMS; //清空
- }
- }
- else
- {
- cnt1 = cnt / ; //该loop一轮处理28个float*float
- cnt2 = (cnt - ( * cnt1)) / ; //该loop一轮处理4个float*float
- cnt3 = (cnt - ( * cnt1) - ( * cnt2)); //该loop一轮处理1个float*float
- _asm
- {
- mov edi, A;
- mov esi, B;
- mov ecx, cnt1;
- jecxz AZERO;
- AL1:
- //movaps XMM, XMM / m128
- //把源存储器内容值送入目的寄存器, 当有m128时, 必须对齐内存16字节, 也就是内存地址低4位为0.
- movaps xmm0, [edi];
- movaps xmm1, [edi + ];
- movaps xmm2, [edi + ];
- movaps xmm3, [edi + ];
- movaps xmm4, [edi + ];
- movaps xmm5, [edi + ];
- movaps xmm6, [edi + ];
- //7*4=28,处理28个float*float
- mulps xmm0, [esi]; //对应点乘
- mulps xmm1, [esi + ];
- mulps xmm2, [esi + ];
- mulps xmm3, [esi + ];
- mulps xmm4, [esi + ];
- mulps xmm5, [esi + ];
- mulps xmm6, [esi + ];
- movaps[edi], xmm0; //回载
- movaps[edi + ], xmm1;
- movaps[edi + ], xmm2;
- movaps[edi + ], xmm3;
- movaps[edi + ], xmm4;
- movaps[edi + ], xmm5;
- movaps[edi + ], xmm6;
- add edi, ;
- add esi, ;
- loop AL1;
- AZERO:
- mov ecx, cnt2;
- jecxz AZERO1;
- AL2:
- movaps xmm0, [edi];
- mulps xmm0, [esi];
- movaps[edi], xmm0;
- add edi, ;
- add esi, ;
- loop AL2;
- AZERO1:
- mov ecx, cnt3;
- jecxz AZERO2;
- mov eax, ;
- AL3:
- movd eax, [edi];
- imul eax, [esi];
- movd[edi], eax;
- add esi, ;
- add edi, ;
- loop AL3;
- AZERO2:
- EMMS;
- }
- }
由于内存对齐的问题,导致末尾有部分数据不正常,特添加C++部分修复。
sse_mul_float c++部分
- int start;
- start = cnt - (cnt % );
- for (int i = start; i < cnt; i++)
- {
- A[i] *= B[i];
- }
用于累加的这个函数,分两块。一块是用指令把大部分数据处理掉,而极少部分数据使用C++语句,这样能各取所长。
sse_acc_float asm部分
- float temp = ;
- int cnt1;
- int cnt2;
- int cnt3;
- int select = ;
- //we process the majority by using SSE instructions
- if (((int)A % )) //unaligned 如果这次调用,内存数据不对齐
- {
- select = ;
- cnt1 = cnt / ;
- cnt2 = (cnt - ( * cnt1)) / ;
- cnt3 = (cnt - ( * cnt1) - ( * cnt2));
- __asm
- {
- mov edi, A;
- mov ecx, cnt1;
- pxor xmm0, xmm0;
- jecxz ZERO;
- L1:
- movups xmm1, [edi];
- movups xmm2, [edi + ];
- movups xmm3, [edi + ];
- movups xmm4, [edi + ];
- movups xmm5, [edi + ];
- movups xmm6, [edi + ];
- //addps 对应相加
- //结果返回目的寄存器
- addps xmm1, xmm2;
- addps xmm3, xmm4;
- addps xmm5, xmm6;
- addps xmm1, xmm5;
- addps xmm0, xmm3;
- addps xmm0, xmm1;
- //至此,xmm0内4个float的和就是24个float的和
- add edi, ;
- loop L1;
- ZERO:
- movd ebx, xmm0; //低4个字节(第一个float)传入ebx
- psrldq xmm0, ; //xmm0右移4字节
- movd eax, xmm0; //右移后,低4个字节(第二个float)传入eax
- movd xmm1, eax; //第一个float传入xmm1低32bit
- movd xmm2, ebx; //第二个float传入xmm2低32bit
- addps xmm1, xmm2; //两个寄存器内4个float对应相加
- movd eax, xmm1; //只取我们要的低位float,传入eax
- movd xmm3, eax; //第一和第二个float的和存在xmm3低32位
- psrldq xmm0, ; //又截掉一个float
- movd ebx, xmm0; //第三个float进ebx
- psrldq xmm0, ; //截掉第三个float
- movd eax, xmm0; //第四个float进eax
- movd xmm1, eax;
- movd xmm2, ebx;
- addps xmm1, xmm2; //第三和第四个float的和存在xmm1低32位
- movd eax, xmm1;
- movd xmm4, eax;
- addps xmm3, xmm4; //4个float的和存在xmm3低32位
- movd eax, xmm3;
- mov temp, eax; //这部分求和存在temp地址区
- EMMS;
- }
- }
- else // aligned 如果这次调用,内存数据对齐
- {
- select = ;
- cnt1 = cnt / ;
- cnt2 = (cnt - ( * cnt1)) / ;
- cnt3 = (cnt - ( * cnt1) - ( * cnt2));
- __asm
- {
- mov edi, A;
- mov ecx, cnt1;
- pxor xmm0, xmm0;
- jecxz ZZERO;
- LL1:
- movups xmm1, [edi];
- movups xmm2, [edi + ];
- movups xmm3, [edi + ];
- movups xmm4, [edi + ];
- movups xmm5, [edi + ];
- movups xmm6, [edi + ];
- addps xmm1, xmm2;
- addps xmm3, xmm4;
- addps xmm5, xmm6;
- addps xmm1, xmm5;
- addps xmm0, xmm3;
- addps xmm0, xmm1;
- add edi, ;
- movups xmm1, [edi];
- movups xmm2, [edi + ];
- movups xmm3, [edi + ];
- movups xmm4, [edi + ];
- movups xmm5, [edi + ];
- movups xmm6, [edi + ];
- addps xmm1, xmm2;
- addps xmm3, xmm4;
- addps xmm5, xmm6;
- addps xmm1, xmm5;
- addps xmm0, xmm3;
- addps xmm0, xmm1;
- add edi, ;
- movups xmm1, [edi];
- movups xmm2, [edi + ];
- addps xmm1, xmm2;
- addps xmm0, xmm1;
- add edi, ;
- loop LL1;
- ZZERO:
- movd ebx, xmm0;
- psrldq xmm0, ;
- movd eax, xmm0;
- movd xmm1, eax;
- movd xmm2, ebx;
- addps xmm1, xmm2;
- movd eax, xmm1;
- movd xmm3, eax;
- psrldq xmm0, ;
- movd ebx, xmm0;
- psrldq xmm0, ;
- movd eax, xmm0;
- movd xmm1, eax;
- movd xmm2, ebx;
- addps xmm1, xmm2;
- movd eax, xmm1;
- movd xmm4, eax;
- addps xmm3, xmm4;
- movd eax, xmm3;
- mov temp, eax;
- EMMS;
- }
- }
sse_acc_float c++部分
- //上面的select记录本次调用sse_acc_float时,数据是否对齐内存
- //下面分情况把剩余的和累加
- int start;
- float c = 0.0f;
- if (select == )
- {
- start = cnt - (cnt % );
- for (int i = start; i < cnt; i++)
- {
- c += A[i];
- }
- }
- else
- {
- start = cnt - (cnt % );
- for (int i = start; i < cnt; i++)
- {
- c += A[i];
- }
- }
- //temp 是用指令计算 ,大部分数据的和
- //c 是用C++语句计算, 所有数据模24或者56剩余部分数据的和
- return(temp + c);
推荐参考:SIMD(单道指令多道数据流)指令(MMX/SSE1/SSE2)详解(中文).
我是一名编程菜鸟,有什么技术上的问题,欢迎讨论和交流指正。谢谢!
获取全部源码:点此 dot_acc.cpp
SIMD学习 -- 用SSE2指令作点乘和累加计算的更多相关文章
- Docker技术入门与实战 第二版-学习笔记-3-Dockerfile 指令详解
前面已经讲解了FROM.RUN指令,还提及了COPY.ADD,接下来学习其他的指令 5.Dockerfile 指令详解 1> COPY 复制文件 格式: COPY <源路径> .. ...
- 学习AngularJs:Directive指令用法(完整版)
这篇文章主要学习AngularJs:Directive指令用法,内容很全面,感兴趣的小伙伴们可以参考一下 本教程使用AngularJs版本:1.5.3 AngularJs GitHub: http ...
- 【js类库AngularJs】学习angularJs的指令(包括常见表单验证,隐藏等功能)
[js类库AngularJs]学习angularJs的指令(包括常见表单验证,隐藏等功能) AngularJS诞生于2009年,由Misko Hevery 等人创建,后为Google所收购.是一款优秀 ...
- Linux学习日志——基本指令②
文章目录 Linux学习日志--基本指令② 前言 touch cp (copy) mv (move) rm vim 输出重定向(> 或 >>) cat df(disk free) f ...
- vue学习04 v-on指令
vue学习04 v-on指令 v-on的作用是为元素绑定事件,比如click单击,dbclick双击 v-on指令可简写为@ 代码测试 <!DOCTYPE html> <html l ...
- vue学习06 v-show指令
目录 vue学习06 v-show指令 v-show指令是:根据真假切换元素的显示状态 原理是修改元素的display,实现显示隐藏 指令后面的内容,最终都会解析为布尔值(true和false) 练习 ...
- vue学习08 v-bind指令
目录 vue学习08 v-bind指令 v-bind指令的作用是为元素绑定属性 完整写法是v-bind:属性名,可简写为:属性名 练习代码为: 运行效果为: vue学习08 v-bind指令 v-bi ...
- SIMD指令集——一条指令操作多个数,SSE,AVX都是,例如:乘累加,Shuffle等
SIMD指令集 from:https://zhuanlan.zhihu.com/p/31271788 SIMD,即Single Instruction, Multiple Data,一条指令操作多个数 ...
- 重温JSP学习笔记--三大指令九大内置对象
最近在温习javaweb的相关基础知识,鉴于我弄丢了记满了整整一本的笔记,决定以后把笔记和一些学习上的心得以及碰到的一些问题统统都放在网上,今天看了一下jsp的相关基础,以下是笔记: JSP三大指令: ...
随机推荐
- BZOJ_2424_[HAOI2010]订货_最小费用最大流
BZOJ_2424_[HAOI2010]订货_最小费用最大流 Description 某公司估计市场在第i个月对某产品的需求量为Ui,已知在第i月该产品的订货单价为di,上个月月底未销完的单位产品要付 ...
- bzoj3262 陌上花开——CDQ分治
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3262 第一道CDQ分治题! 看博客:https://www.cnblogs.com/Narh ...
- nginx开发(四)调用ffmpeg,搭建rtmp直播流。
1: 修改conf文件,配置rtmp直播 打开usr/local/nginx/conf/nginx.conf,添加红色内容: rtmp {#rtmp点播配置 server { li ...
- java笔记线程方式1获取对象名称
* 如何获取线程对象的名称呢? * public final String getName():获取线程的名称. * 如何设置线程对象的名称呢? * public final void setName ...
- 《Effective C++》笔记:III(转载)
转自:http://www.cnblogs.com/destino74/p/3960802.html 条款5:Know what functions C++ silently writes and c ...
- Spring Theme简单应用
Spring MVC特性里由一个是关于Spring Theme主题的应用,所以写了个Demo 1.这里先看项目结构(Meven项目) 2.所需的POM依赖 <dependency> < ...
- bzoj 4808: 马【匈牙利算法】
网格图黑白染色,然后能互相攻击到的点之间连边,跑匈牙利算法最大匹配,答案是好点个数-最大匹配(最大独立集) 注意pao的时候只从一种颜色的格子统计即可 #include<iostream> ...
- [App Store Connect帮助]四、添加 App 图标、App 预览和屏幕快照(2)添加一个 App Store 图标
您必须提供一个 App Store 图标,用于在 App Store 中的不同部分展示您的 App.请遵照 Human Interface Guidelines(<人机界面准则>)创建您的 ...
- 乐字节Java8核心特性之方法引用
大家好,我是乐字节的小乐,上一次我们说到了Java8核心特性之函数式接口,接下来我们继续了解Java8又一核心特性--方法引用. Java8 中引入方法引用新特性,用于简化应用对象方法的调用, 方法引 ...
- Android之条形码、二维码扫描框架(非原创)
文章大纲 一.条形码.二维码扫描框架介绍二.条形码.二维码的区别和组成结构介绍三.条形码.二维码扫描框架应用场景四.BGAQRCode-Android框架实战五.项目源码下载六.参考文章 一.条形码. ...