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三大指令: ...
随机推荐
- Massive Data Mining学习记录
第一周: 学习PageRank, 知识点:每个节点的权值由其他节点的投票决定,所有节点的权值和为1 当节点很多时候必须转换成矩阵运算来计算节点的最终值,由马尔可夫链可以证明,这个值可以迭代得到 问题: ...
- 编译android的一些坑
1 降级gcc g++到4.4 2 参考:http://source.android.com/source/initializing.html来配置环境 3 使用jdk1.6 包括 java java ...
- android压力测试命令monkey详解【转】
本文转载自:http://www.jb51.net/article/48557.htm 作者: 字体:[增加 减小] 类型:转载 时间:2014-03-29我要评论 这篇文章主要介绍了android ...
- Python基础第二天
一.内容 二.练习 练习1 题目:已知msg='hello knight 666'编写for循环,利用索引遍历出每一个字符 图示: 代码: msg = 'hello knight 666' msg_l ...
- 如何在BCGControlBar工程的工具栏里面新增下拉列表控件
通常情况下,工具栏里面都是一些按钮和图片,很少可以看到下拉列表控件,但是在某些应用场合,也需要用到下拉列表控件.今天在这里就简单讲解下如何在工具栏里添加下拉列表控件. 添加的过程也比较简单,在CM ...
- MogileFS介绍
MogileFS介绍 MogileFS 是一个开源的分布式文件存储系统,由LiveJournal旗下的Danga Interactive公司开发. Danga团队开发了包括 Memcached.Mog ...
- java虚拟机全集(31篇文章)
深入理解java虚拟机系列 深入理解Java虚拟机笔记---内存区域 深入理解Java虚拟机笔记---判断对象是否存活 深入理解Java虚拟机笔记---垃圾收集算法 深入理解Java虚拟机笔记---垃 ...
- 台哥原创:java 连连看源码
2010年,迷上了玩连连看 随手就做了这个,正好手头有这些图片素材 游戏启动时,界面先铺上了一层透明幕布,然后这些兵器图片交替从上到下,从左到右出现.. 鼠标停在兵器格子上时,所在格子会有红色 ...
- ES6 学习笔记 - let和const
let 和 const 命令 学习资料:ECMAScript 6 入门 let 所声明的变量,只在let命令所在的代码块内有效.用途:循环计数器. 如果使用var,只有一个全局变量i: var a = ...
- P1165 日志分析
题目描述 M 海运公司最近要对旗下仓库的货物进出情况进行统计.目前他们所拥有的唯一记录就是一个记录集装箱进出情况的日志.该日志记录了两类操作:第一类操作为集装箱入库操作,以及该次入库的集装箱重量:第二 ...