基於SSE4和多核編程的電子相冊的實現git
摘要:電子相冊中先後兩張圖片的切換會產生淡入淡出效果,並且切換過程當中須要大量的中間計算過程,而SSE4和多核編程技術可以有效加快中間的計算過程,有效減小圖片之間切換時間,本文將對基於SSE4和多核編程的電子相冊的實現過程進行詳細說明。github
關鍵詞:電子相冊;淡入淡出;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的像素值數列lp一、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,可取四、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;
}