这几天在做学校的一个学习小项目,需要用到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指令计算点乘和累加的更多相关文章

  1. AVX 指令详解 ,还有SSE指令

    https://blog.csdn.net/fengbingchun/article/details/23598709 本人从来不复制的,自己看!.

  2. SIMD指令集——一条指令操作多个数,SSE,AVX都是,例如:乘累加,Shuffle等

    SIMD指令集 from:https://zhuanlan.zhihu.com/p/31271788 SIMD,即Single Instruction, Multiple Data,一条指令操作多个数 ...

  3. SIMD学习 -- 用SSE2指令作点乘和累加计算

    这几天在做学校的一个学习小项目,需要用到SIMD指令计算提速.也是第一次碰这个,看了一些资料和代码,模仿着写了两个函数. void sse_mul_float(float *A, float *B, ...

  4. SSE图像算法优化系列十七:多个图像处理中常用函数的SSE实现。

    在做图像处理的SSE优化时,也会经常遇到一些小的过程.数值优化等代码,本文分享一些个人收藏或实现的代码片段给大家. 一.快速求对数运算 对数运算在图像处理中也是个经常会遇到的过程,特备是在一些数据压缩 ...

  5. SSE图像算法优化系列十八:三次卷积插值的进一步SSE优化。

    本文是在学习https://blog.csdn.net/housisong/article/details/1452249一文的基础上对算法的理解和重新整理,再次非常感谢原文作者的深入分析以及分享. ...

  6. SSE图像算法优化系列二十一:基于DCT变换图像去噪算法的进一步优化(100W像素30ms)。

    在优化IPOL网站中基于DCT(离散余弦变换)的图像去噪算法(附源代码) 一文中,我们曾经优化过基于DCT变换的图像去噪算法,在那文所提供的Demo中,处理一副1000*1000左右的灰度噪音图像耗时 ...

  7. SSE图像算法优化系列十四:局部均方差及局部平方差算法的优化。

    关于局部均方差有着较为广泛的应用,在我博客的基于局部均方差相关信息的图像去噪及其在实时磨皮美容算法中的应用及使用局部标准差实现图像的局部对比度增强算法中都有谈及,即可以用于去噪也可以用来增强图像,但是 ...

  8. SSE图像算法优化系列五:超高速指数模糊算法的实现和优化(10000*10000在100ms左右实现)。

    今天我们来花点时间再次谈谈一个模糊算法,一个超级简单但是又超级牛逼的算法,无论在效果上还是速度上都可以和Boxblur, stackblur或者是Gaussblur想媲美,效果上,比Boxblur来的 ...

  9. SSE图像算法优化系列一:一段BGR2Y的SIMD代码解析。

    一个同事在github上淘到一个基于SIMD的RGB转Y(彩色转灰度或者转明度)的代码,我抽了点时间看了下,顺便学习了一些SIMD指令,这里把学习过程中的一些理解和认识共享给大家. github上相关 ...

随机推荐

  1. 一个web应用的诞生--数据存储

    上一章实现了登录的部分功能,之所以说是部分功能,是因为用户名和密码写成固定值肯定是不可以的,一个整体的功能,至少需要注册,登录,密码修改等,这就需要提供一个把这些值存储到数据库的能力. 当前的主流数据 ...

  2. Livy原理详解

    Livy的概述(引自社区) Livy(当前是alpha版本)是一个提供rest接口和spark集群交互的服务.它可以提交spark job或者spark一段代码,同步或者异步的返回结果:也提供spar ...

  3. uml的图与代码的转换——类图

    Uml是我们经常使用的统一建模语言或称标准建模语言.它的图是如何和代码对应的呢?下面我们就来就这个问题讨论一下: 首先是类:uml中的类图是这样的 在这个图中,我们可以看出,这个类图总共分了三行,第一 ...

  4. JavaScript null 和 undefined

    null null 表示一个变量被声明了,并被赋值为空 var lzh = null; console.log(lzh); // null console.log(typeof lzh); // ob ...

  5. 在 Windows 上安装 Hadoop 教程(转)

    在 Windows 上安装 Hadoop 教程 一见 2010.1.6 www.hadoopor.com/hadoopor@foxmail.com 1. 安装 JDK 不建议只安装 JRE,而是建议直 ...

  6. GET和POST的区别,何时使用POST?

     GET:一般用于信息获取,使用URL传递参数,对所发送信息的数量也有限制,一般在2000个字符     POST:一般用于修改服务器上的资源,对所发送的信息没有限制.     GET方式需要使用Re ...

  7. mysqldump 使用说明

    mysqldump 使用说明 A Database Backup Program mysqldump客户端是一款实用的mysql备份程序,可以对数据库的定义及数据表内容,进行备份生成相应的SQL语句. ...

  8. 项目中通过Sorlj获取索引库中的数据

    在开发项目中通过使用Solr所提供的Solrj(java客户端)获取索引库中的数据,这才是真正对项目起实质性作用的功能,提升平台的检索性能及检索结果的精确性 第一步,引入相关依赖的jar包 第二步,根 ...

  9. 使用jQuery操作DOM

    一.DOM操作分为3类 1.DOM Core DOM Core不是Javascript的专属品,任何一种支持DOM的编程语言都可以使用它.它的用途不仅限于处理一种使用标记语言编写出来的文档 2.HTM ...

  10. 【openstack N版】——走进云计算

    一.云计算 云计算是一种按使用量付费的模式,这种模式提供可用的.便捷的.按需的网络访问,进入可配置的计算资源共享池(资源包括:网络.服务器.存储.应用软件.服务),这些资源能够被快速提供,只需投入很少 ...