基于SSE4和多核编程的电子相册的实现
基于SSE4和多核编程的电子相册的实现
摘要:电子相册中前后两张图片的切换会产生淡入淡出效果,而且切换过程中需要大量的中间计算过程,而SSE4和多核编程技术能够有效加快中间的计算过程,有效减少图片之间切换时间,本文将对基于SSE4和多核编程的电子相册的实现过程进行详细说明。
关键词:电子相册;淡入淡出;SSE4;多核编程
1. 引言
在电子相册中,前后两张图片,由前一张图片完全切换为后一张图片的过程中,如果将中间结果展现出来的话,就会出现图片的淡入淡出的效果,但是由于像素较高的图片之间切换过程需要大量的计算过程,使用普通的串行程序完成整个切换过程需要消耗较长的时间,而考虑使用SSE4和多核编程将使整个切换过程所需的时间显著减少,本次实验我们具体验证通过使用这两项技术对图片的切换过程产生怎样的影响。
2. 关键技术
2.1 SSE4
SSE4指令集的英文全称是:Streaming SIMD Extensions 4,是英特尔自从SSE2之后对ISA扩展指令集最大的一次的升级扩展。除了将延续多年的32位架构升级至64位之外,还加入了图形、视频编码、字符串/文本处理、三维游戏应用等指令,使得处理器不但在多媒体,而且在文本处理、矢量化编译、特定应用等方面的应用性能都得到了大幅度的提升。新指令集增强了从多媒体应用到高性能计算应用领域的性能,同时还利用一些专用电路实现对于特定应用加速。SSE4.1版本的指令集新增加了47条指令,主要针对向量绘图运算、3D游戏加速、视频编码加速及协同处理的加速。英特尔方面指出,在应用SSE4指令集后,45纳米Penryn核心额外提供了2个不同的32位向量整数乘法运算支持,并且在此基础上还引入了8位无符号最小值和最大值以及16位、32位有符号和无符号的运算,能够有效地改善编译器编译效率,同时提高向量化整数和单精度运算地能力。SSE4.2则是在新一代Nehalem架构基于Core微架构的SSE4.1指令集上,新增的7组指令主要针对字符串和文本处理指令应用。
2.2 多核编程
多核编程的基本目的是通过多个任务的并行执行提高应用程序的性能。这就需要将一个应用程序进行任务划分:尽量分解为多个相对独立的任务,每个任务实现一个线程,从而将多个任务分布到多个计算核上执行,减少程序的执行时间。并行程序的设计模式大致分为:并发性发现、算法结构设计、结构支撑和实现机制4个步骤。首先要分析待解决的问题是否值得去并行,然后确定问题的主要特征和数据元素,最后识别问题的哪一部分是计算最密集的。通过算法结构的设计,将并发性映射到多个线程或进程,进一步向并行程序靠近。
Windows环境下的多线程编程:MFC用类库的方式将Win32API进行封装,提供对多线程的支持。MFC有两种类型的线程:用户界面线程和辅助线程。
OpenMP是一个针对共享内存架构的多线程编程标准,用于编程可移植的多线程应用程序。OpenMP程序设计模型提供了一组与平台无关的编译指导、指导命令、函数调用和环境变量,显示地指导编译器开发程序中的并行性。对于很多循环来说,都可以在循环体之前插入一条编译指导语句,使其以多线程执行。
3. 实验思路
(1) 由于C++可以直接使用内嵌原语,故本实验采用C++语言编写。
(2) 使用C++的基于对话框的MFC程序实现图像的操作及相应的图像淡入淡出效果。
4. 实验环境
(1) 操作系统:win7 32位
(2) 编程工具:Visual Studio 2017
(3) 编程语言:C++
(4) 图片规格:*.BMP格式的文件,分辨率为1280×800
5. 实验过程及代码实现
5.1 加载图像到内存
为了贴合多核思想,本实验所采用的6张图片都是BMP格式的图片,它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的空间很大,满足实验要求。本实验设置了比较功能,即在普通情况下的图片切换,所以需要两个存储位图的数组,加载图像核心代码如下:
#define MAX_COUNT 6
CBitmap bmps[MAX_COUNT];
CBitmap mmp[MAX_COUNT];
for (int i = 0; i < MAX_COUNT; i++)
{
bmps[i].LoadBitmap(IDB_BITMAP1 + i);
}
for (int i = 0; i < MAX_COUNT; i++)
{
mmp[i].LoadBitmap(IDB_BITMAP1 + i);
}
5.2 获取图像的像素点
在淡入淡出的过程中,需要对像素点进行融合。首先需要获取图像的像素点。核心代码如下:
BITMAP b; //声明位图文件b
bmps[m].GetBitmap(&b);
//获取第一张图片
int size = b.bmHeight*b.bmWidthBytes; //获取位图字节数
BYTE *lp1 = new BYTE[size];
bmps[m].GetBitmapBits(size, lp1); //获取第一张图片的像素列
BITMAP b2; //声明位图文件b2
bmps[n].GetBitmap(&b2); //获取第二张图片,由于图片大小相等,所以不用重复计算字节数
BYTE *lp2 = new BYTE[size];
bmps[n].GetBitmapBits(size, lp2); //获取第二张图片的像素列
//GetBitmapBits()函数将指定位图的位拷贝到缓冲区里。
BYTE *lp3 = new BYTE[size]; //用来显示中间图像
}
此方法分别获取图像A和图像B的像素值数列lp1、lp2,并定义lp3用来存储中间图像的像素。
5.3 实现图像的淡入淡出
像素点的融合公式:
Result_pixel=A_pixel×fade+B_pixel×(1-fade)
将上式做简单变换,减少一次乘法,得:
Result_pixel=(A_pixel-B_pixel)×fade+B_pixel
在fade因子从127渐变为0的过程中,产生了一系列A、B融合的中间图像,实现了由A图像淡化到B图像的过程。在实际计算过程中,为了方便计算,fade因子从127渐变为0,计算完成后再右移7位,以此达到从1渐变为0的效果。一个像素为4个字节32位,解紧缩后为4个字64位;SSE4寄存器为128位,可存储8个字,即2个解紧缩后的像素,故可用于同时计算两个像素,加快运行速度。SSE4内嵌原语的使用需在程序加入以下语句:
#include <nmmintrin.h>
同时在循环过程中,内层嵌套每次递增8个,即4个两像素同时计算,体现了多核思想,进一步加快了融合速度。
内层嵌套递增数值的要求:1)为宽度的约数,如本例中宽度为1280,可取4、8等;2)应符合机器128位寄存器个数要求,如本例需使用10个寄存器,8个用于像素值的存储、计算,1个用于fade值的存储(xmm0),1个用于紧缩及解紧缩操作(xmm8),以减少可能的数据及结构冲突。
在循环开始前,利用#pragma omp parallel for private(4,x)对X进行四线程并行计算,进一步体现了多核思想。
核心代码如下:
for (int fade = 127; fade >= 0; fade--) //
{
for (int y = 0; y < b.bmHeight; y++)
{
#pragma omp parallel forprivatc(4,x) //4线程并行计算
for (int x = 0; x < b.bmWidth; x = x + 8) //4个两像素同时计算
{
rgb[0] = y*b.bmWidthBytes + x * 4;
rgb[1] = y*b.bmWidthBytes + (x + 1) * 4;
rgb[2] = y*b.bmWidthBytes + (x + 2) * 4;
rgb[3] = y*b.bmWidthBytes + (x + 3) * 4;
rgb[4] = y*b.bmWidthBytes + (x + 4) * 4;
rgb[5] = y*b.bmWidthBytes + (x + 5) * 4;
rgb[6] = y*b.bmWidthBytes + (x + 6) * 4;
rgb[7] = y*b.bmWidthBytes + (x + 7) * 4;
//A的两个像素8字节
pic1[0] = lp1[rgb[0]];
pic1[1] = lp1[rgb[0] + 1];
pic1[2] = lp1[rgb[0] + 2];
pic1[3] = lp1[rgb[0] + 3];
pic1[4] = lp1[rgb[1]];
pic1[5] = lp1[rgb[1] + 1];
pic1[6] = lp1[rgb[1] + 2];
pic1[7] = lp1[rgb[1] + 3];
//B的两个像素8字节
pic2[0] = lp2[rgb[0]];
pic2[1] = lp2[rgb[0] + 1];
pic2[2] = lp2[rgb[0] + 2];
pic2[3] = lp2[rgb[0] + 3];
pic2[4] = lp2[rgb[1]];
pic2[5] = lp2[rgb[1] + 1];
pic2[6] = lp2[rgb[1] + 2];
pic2[7] = lp2[rgb[1] + 3];
//余下的3个两像素的定义相同
__m128i xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6,xmm7,xmm9;
__m128i xmm8 = _mm_setzero_si128();
//初始化寄存器
xmm0 = _mm_set_epi16(fade, fade, fade, fade, fade, fade, fade, fade);
//8个fade因子装入寄存器
xmm1 = _mm_loadu_si128((__m128i*)pic1);//A的两个像素分量装入寄存器
xmm1 = _mm_unpacklo_epi8(xmm1, xmm8);//8个一位解紧缩至16位
xmm2 = _mm_loadu_si128((__m128i*)pic2);//B的两个像素分量装入寄存器
xmm2 = _mm_unpacklo_epi8(xmm2, xmm8);//8个一位解紧缩至16位
xmm1 = _mm_sub_epi16(xmm1, xmm2); //A-B
xmm1 = _mm_mullo_epi16(xmm1, xmm0);//8个16位乘法
xmm1 = _mm_srai_epi16(xmm1, 7);//右移7位,相当于除127
xmm1 = _mm_add_epi16(xmm1, xmm2);//加法
xmm1 = _mm_packus_epi16(xmm1,xmm8);//16个一位紧缩为8位
//依次提取每个整型,放入待显示像素数组
lp3[rgb[0]] = _mm_extract_epi8(xmm1, 0);
lp3[rgb[0] + 1] = _mm_extract_epi8(xmm1, 1);
lp3[rgb[0] + 2] = _mm_extract_epi8(xmm1, 2);
lp3[rgb[0] + 3] = _mm_extract_epi8(xmm1, 3);
lp3[rgb[1]] = _mm_extract_epi8(xmm1, 4);
lp3[rgb[1] + 1] = _mm_extract_epi8(xmm1, 5);
lp3[rgb[1] + 2] = _mm_extract_epi8(xmm1, 6);
lp3[rgb[1] + 3] = _mm_extract_epi8(xmm1, 7);
//余下3个两像素的计算类似
}
}
bitmap.SetBitmapBits(size, lp3);
Ondraw();
}
delete lp1;
delete lp2;
delete lp3;
}
源码下载:https://github.com/lyj8330328/Album
基于SSE4和多核编程的电子相册的实现的更多相关文章
- 初探Lambda表达式/Java多核编程【2】并行与组合行为
今天又翻了一下书的目录,第一章在这之后就结束了.也就是说,这本书所涉及到的新的知识已经全部点到了. 书的其余部分就是对这几个概念做一些基础知识的补充以及更深层次的实践. 最后两个小节的内容较少,所以合 ...
- PAIP.并发编程 多核编程 线程池 ExecutorService的判断线程结束
PAIP.并发编程 多核编程 线程池 ExecutorService的判断线程结束 ExecutorService并没有提供什么 isDone()或者isComplete()之类的方法. 作者Atti ...
- paip.提升性能---并行多核编程哈的数据结构list,set,map
paip.提升性能---并行多核编程哈的数据结构list,set,map vector/copyonwritearraylist 都是线程安全的. 或者经过包装的list ::: collection ...
- .NET 并行(多核)编程系列之七 共享数据问题和解决概述
原文:.NET 并行(多核)编程系列之七 共享数据问题和解决概述 .NET 并行(多核)编程系列之七 共享数据问题和解决概述 前言:之前的文章介绍了了并行编程的一些基础的知识,从本篇开始,将会讲述并行 ...
- .NET 并行(多核)编程系列之五 Task执行和异常处理
原文:.NET 并行(多核)编程系列之五 Task执行和异常处理 .NET 并行(多核)编程系列之五 Task执行和异常处理 前言:本篇主要讲述等待task执行完成. 本篇的议题如下: 1. 等待Ta ...
- .NET 并行(多核)编程系列之六 Task基础部分完结篇
原文:.NET 并行(多核)编程系列之六 Task基础部分完结篇 .NET 并行(多核)编程系列之六 Task基础部分完结篇 前言:之前的文章介绍了了并行编程的一些基本的,也注重的讲述了Task的一些 ...
- 初探Lambda表达式/Java多核编程【0】从外部迭代到内部迭代
开篇 放假前从学校图书馆中借来一本书,Oracle官方的<精通Lambda表达式:Java多核编程>. 假期已过大半才想起来还没翻上几页,在此先推荐给大家. 此书内容及其简洁干练,如果你对 ...
- 初探Lambda表达式/Java多核编程【1】从集合到流
从集合到流 接上一小节初探Lambda表达式/Java多核编程[0]从外部迭代到内部迭代,本小节将着手使用"流"这一概念进行"迭代"操作. 首先何为" ...
- 初探Lambda表达式/Java多核编程【3】Lambda语法与作用域
接上一篇:初探Lambda表达式/Java多核编程[2]并行与组合行为 本节是第二章开篇,前一章已经浅显地将所有新概念点到,书中剩下的部分将对这些概念做一个基础知识的补充与深入探讨实践. 本章将介绍L ...
随机推荐
- LeetCode & Q20-Valid Parentheses-Easy
Stack String Description: Given a string containing just the characters '(', ')', '{', '}', '[' and ...
- SpringMVC之数据传递一
之前的博客中也说了,mvc中数据传递是最主要的一部分,从url到Controller.从view到Controller.Controller到view以及Controller之间的数据传递.今天主要学 ...
- Jquery blokckUI 快速入门
$("#btnSubmit").click(function() { $.blockUI({ message : $("#loginForm"), css : ...
- 其他—cooki和session
cookie Cookie的由来 大家都知道HTTP协议是无状态的. 无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不 ...
- AWS的开发工具包和设备SDK开发工具包
一.开发工具包 二.设备sdk开发工具包
- LXC学习实践(2)安装LXC
1.准备工作: yum install gcc yum install libcap-devel yum install libcgroup 2.安装LXC 下载源代码:sourceforge.net ...
- Spring Security 入门(3-10)Spring Security 的四种使用方式
原文链接: http://www.360doc.com/content/14/0724/17/18637323_396779659.shtml 下面是作者的一个问题处理
- 详解Windows Server 2008 R2下安装Oracle 11g
本篇文章转载 http://www.it165.net/database/html/201212/3385.html 一.安装前的准备工作: 1. 修改计算机名: 服务器的计算机名称对于登录到Orac ...
- memcached企业面试题
面试题如下: 1.Memcached是什么,有什么作用?Memcached是一个开源的,高性能的内存绶存软件,从名称上看Mem就是内存的意思,而Cache就是缓存的意思. Memcached的作用:通 ...
- 一大波 Android 刘海屏来袭,全网最全适配技巧!
一.序 Hi,大家好,我是承香墨影! Apple 一直在引领设计的潮流,自从 iPhone X 发布之后,"刘海屏" 就一直存在争议.不过不管你怎样,Android 也要跻入 &q ...