用SSE指令计算点乘和累加
这几天在做学校的一个学习小项目,需要用到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
用SSE指令计算点乘和累加的更多相关文章
- AVX 指令详解 ,还有SSE指令
https://blog.csdn.net/fengbingchun/article/details/23598709 本人从来不复制的,自己看!.
- SIMD指令集——一条指令操作多个数,SSE,AVX都是,例如:乘累加,Shuffle等
SIMD指令集 from:https://zhuanlan.zhihu.com/p/31271788 SIMD,即Single Instruction, Multiple Data,一条指令操作多个数 ...
- SIMD学习 -- 用SSE2指令作点乘和累加计算
这几天在做学校的一个学习小项目,需要用到SIMD指令计算提速.也是第一次碰这个,看了一些资料和代码,模仿着写了两个函数. void sse_mul_float(float *A, float *B, ...
- SSE图像算法优化系列十七:多个图像处理中常用函数的SSE实现。
在做图像处理的SSE优化时,也会经常遇到一些小的过程.数值优化等代码,本文分享一些个人收藏或实现的代码片段给大家. 一.快速求对数运算 对数运算在图像处理中也是个经常会遇到的过程,特备是在一些数据压缩 ...
- SSE图像算法优化系列十八:三次卷积插值的进一步SSE优化。
本文是在学习https://blog.csdn.net/housisong/article/details/1452249一文的基础上对算法的理解和重新整理,再次非常感谢原文作者的深入分析以及分享. ...
- SSE图像算法优化系列二十一:基于DCT变换图像去噪算法的进一步优化(100W像素30ms)。
在优化IPOL网站中基于DCT(离散余弦变换)的图像去噪算法(附源代码) 一文中,我们曾经优化过基于DCT变换的图像去噪算法,在那文所提供的Demo中,处理一副1000*1000左右的灰度噪音图像耗时 ...
- SSE图像算法优化系列十四:局部均方差及局部平方差算法的优化。
关于局部均方差有着较为广泛的应用,在我博客的基于局部均方差相关信息的图像去噪及其在实时磨皮美容算法中的应用及使用局部标准差实现图像的局部对比度增强算法中都有谈及,即可以用于去噪也可以用来增强图像,但是 ...
- SSE图像算法优化系列五:超高速指数模糊算法的实现和优化(10000*10000在100ms左右实现)。
今天我们来花点时间再次谈谈一个模糊算法,一个超级简单但是又超级牛逼的算法,无论在效果上还是速度上都可以和Boxblur, stackblur或者是Gaussblur想媲美,效果上,比Boxblur来的 ...
- SSE图像算法优化系列一:一段BGR2Y的SIMD代码解析。
一个同事在github上淘到一个基于SIMD的RGB转Y(彩色转灰度或者转明度)的代码,我抽了点时间看了下,顺便学习了一些SIMD指令,这里把学习过程中的一些理解和认识共享给大家. github上相关 ...
随机推荐
- Bootstrap WPF Style(二)--Glyphicons 字体图标
介绍 关于Glyphicons字体图标,首先给出友情链接 Glyphicons 这个项目是在Bootstrap WPF Style项目基础上做的,详见http://www.cnblogs.com/ts ...
- Yii2前后台分离
Yii2前后台都需要注册的时候会产生前后台登录一个另一个同步登录和退出,这是因为登录和退出之前的sitecontroller里面公用了common/model下面的LoginForm.php和user ...
- 代码神器Atom,最常用的几大插件,你值得拥有。
作者:魔洁 atom常用插件 atom插件安装File>Settings>intall搜索框输入插件名,点击Packages搜索,搜索出来后点击intall安装,建议你先安装(simpli ...
- Spark源码分析之Spark Shell(上)
终于开始看Spark源码了,先从最常用的spark-shell脚本开始吧.不要觉得一个启动脚本有什么东东,其实里面还是有很多知识点的.另外,从启动脚本入手,是寻找代码入口最简单的方法,很多开源框架,其 ...
- Ioc容器依赖注入-Spring 源码系列(2)
Ioc容器依赖注入-Spring 源码系列(2) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostPr ...
- Python thread local
由于GIL的原因,笔者在日常开发中几乎没有用到python的多线程.如果需要并发,一般使用多进程,对于IO Bound这种情况,使用协程也是不错的注意.但是在python很多的网络库中,都支持多线程, ...
- C++用new创建对象和不用new创建对象的区别解析
在C++用new创建对象和不用new创建对象是有区别的,不知你是否清楚的了解它们到底有什么样的区别呢?下面小编就用示例来告诉大家吧,需要的朋友可以过来参考下 我们都知道C++中有三种创建对象的方法 ...
- 如何通过注解Bean类来封装SQL插入语句
整体思路是酱紫的: 给bean上注解说明该bean对应着数据库中哪张表,给每个bean的属性都注解说明各自对应着这张表的哪个字段. 通过类反射获取表名,通过逐个反射每个属性的getter方法,获取注解 ...
- mybatis的动态sql中collection与assoction
User.java, Role.java,address.java为三个类 public class User { .....//user自己的属性//association一对一 private R ...
- 使用StyleCop.Analyzers进行代码审查
为什么要进行代码审核? 提早发现代码中的BUG,避免将BUG带到生产环境 极大的提高软件质量,以及可维护性 统一代码规范.提高可读性,减少新加入成员的熟悉时间 加速个人和团队的成长,知识和经验的积累 ...