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三大指令: ...
随机推荐
- hive使用
运行hadoop [root@hadoop0 ~]# start-all.sh 进入命令行[root@hadoop0 ~]# hive 查询昨天的表 hive> select * from st ...
- POJ1912 A highway and the seven dwarfs (判断凸包与直线相交 logn)
POJ1912 给定n个点 和若干条直线,判断对于一条直线,是否存在两个点在直线的两侧. 显然原命题等价于 凸包与直线是否相交. O(n)的算法是显而易见的 但是直线数量太多 就会复杂到O(n^2)由 ...
- CF 1036 B Diagonal Walking v.2 —— 思路
题目:http://codeforces.com/contest/1036/problem/B 题意:从 (0,0) 走到 (n,m),每一步可以向八个方向走一格,问恰好走 k 步能否到达,能到达则输 ...
- 手推Apriori算法------挖掘频繁项集
版权声明:本文为博主原创文章,未经博主允许不得转载. Apriori算法: 使用一种称为逐层搜索的迭代方法,其中K项集用于搜索(K+1)项集. 首先,通过扫描数据库,统计每个项的计数,并收集满足最小支 ...
- Java多线程系列一——Java实现线程方法
Java实现线程的两种方法 继承Thread类 实现Runnable接口 它们之间的区别如下: 1)Java的类为单继承,但可以实现多个接口,因此Runnable可能在某些场景比Thread更适用2) ...
- Flask-SQLAlchemy - 不使用外键连表查询。记得常回来看我
前言 相比于 Django 的 ORM ,SQLAlchemy "不依靠外键进行跨表联查" 的解决方案就比较多. 没啥好说的,只能怪自己学艺不精.. _(:з」∠)_ 解决办法 ...
- uva 11292 The Dragon of Loowater(贪心)
题目大意: 你的王国里有一条n个头的恶龙,你希望雇一些骑士把它杀死(即砍掉所有头).村里有m个骑士可以雇佣,一个能力值为x的骑士可以砍掉恶龙一个直径不超过x的头,且需要支付x个金币.如何雇佣骑士才 ...
- c#自定义ORM框架---(泛型&反射&实体类扩展属性<附带通用增、删、查、改>)
该教材主要是运用到泛型.反射和实体类扩展属性 步骤一.建立扩展属性类 实体类扩展属性要继承Attribute基类完成 [AttributeUsage(AttributeTargets.Property ...
- U - Relatives(欧拉函数)
Description Given n, a positive integer, how many positive integers less than n are relatively prime ...
- ACM_拼接数字
拼接数字 Time Limit: 2000/1000ms (Java/Others) Problem Description: 给定一个正整数数组,现在把数组所有数字都拼接成一个大数字,如何使得拼接后 ...