日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > c/c++ >内容正文

c/c++

深入探讨用位掩码代替分支(3):VC6速度测试

發(fā)布時(shí)間:2023/12/2 c/c++ 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入探讨用位掩码代替分支(3):VC6速度测试 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

  wuhanbingwhdx提到了數(shù)據(jù)相關(guān)也會(huì)影響流水線(http://blog.csdn.net/zyl910/article/details/1330614)。
  他的說(shuō)法是有一定道理的。但是,在很多時(shí)候我們并不僅僅處理一個(gè)數(shù)值。比如將循環(huán)展開(kāi),在內(nèi)循環(huán)處理2個(gè)或更多個(gè)的數(shù)值。而現(xiàn)代編譯器面對(duì)循環(huán)展開(kāi)時(shí),在編譯優(yōu)化操作中會(huì)調(diào)整指令順序,錯(cuò)開(kāi)有相關(guān)性指令。因現(xiàn)代處理器支持超標(biāo)量,這樣的指令順序調(diào)整能獲得較好的指令級(jí)并行度,從而優(yōu)化了性能。
  其次,就算編譯器對(duì)循環(huán)展開(kāi)優(yōu)化的不夠徹底,沒(méi)將相關(guān)性指令錯(cuò)開(kāi)。但因現(xiàn)代處理器支持亂序執(zhí)行,當(dāng)遇到相關(guān)性指令需要等待時(shí),處理器會(huì)處理后面未相關(guān)的指令,從而保持處理器滿(mǎn)載盡量減輕相關(guān)性等待造成的性能損失。
  第三,現(xiàn)代處理器還支持寄存器重命名技術(shù)——當(dāng)兩處代碼用到同名的寄存器時(shí),譯碼器會(huì)做寄存器重命名處理給它們分配不同的寄存器,使數(shù)據(jù)不會(huì)干擾,從而獲得更高的指令級(jí)并行度。

  上面說(shuō)了很多理論知識(shí),實(shí)際性能到底怎么樣呢?還是寫(xiě)代碼測(cè)一測(cè)吧。


一、目標(biāo)——將64位像素轉(zhuǎn)為32位像素

  將64位像素轉(zhuǎn)為32位像素,是飽和處理的最典型應(yīng)用。
  64位像素有4個(gè)通道,每個(gè)通道16位(帶符號(hào)16位整數(shù))。每像素8個(gè)字節(jié)。
  32位像素有4個(gè)通道,每個(gè)通道8位(無(wú)符號(hào)8位整數(shù))。每像素4個(gè)字節(jié)。

  轉(zhuǎn)換方法為——將每個(gè)像素的4個(gè)通道由16位(帶符號(hào)16位整數(shù))轉(zhuǎn)為8位(無(wú)符號(hào)8位整數(shù))。因?yàn)槊看味际菍?duì)4個(gè)通道進(jìn)行處理,所以能獲得較高的指令級(jí)并行度。

  具體的存儲(chǔ)格式為——

注:
1.內(nèi)存地址由低到高(從下到上),垂直方向的每一格是一個(gè)字節(jié)。+0代表數(shù)據(jù)基址,+1代表數(shù)據(jù)基址+1,以此類(lèi)推。
2.左為“64位像素”數(shù)組,右為“32位像素”數(shù)組。
3.圖中使用了用雙邊線來(lái)分隔像素。因“64位像素”是8字節(jié),而“32位像素”是4字節(jié)。所以對(duì)于16個(gè)字節(jié)的空間,左側(cè)能存放2個(gè)像素、右側(cè)能存放4個(gè)像素。
4.圖中使用了用實(shí)線來(lái)分隔通道?!?4位像素”的通道是16位,占用2個(gè)字節(jié)。“32位像素”的通道是8位,只占用1個(gè)字節(jié)。
5.圖中使用了用虛線來(lái)分隔字節(jié)。主要用于“64位像素”。
6.這里采用了Windows位圖通道規(guī)則,即通道順序?yàn)锽、G、R、A(從低到高)。例如:“B0”代表像素0的B(藍(lán)色)通道、“A0”代表像素0的A(不透明度)通道、“A1”代表像素1的A通道……以此類(lèi)推。
7.這里采用了小端(Little Endian)方式的字節(jié)序(Endianness),即最低地址存放的最低字節(jié)。采用小寫(xiě)的“h”、“l(fā)”來(lái)表示“64位像素”通道的高、低字節(jié)。例如:“B0l”代表像素0的B通道的低字節(jié)、“A0h”代表像素0的A通道的高字節(jié)……以此類(lèi)推。

  上面貌似挺復(fù)雜,又是圖表又是大段文字的。其實(shí),代碼寫(xiě)起來(lái)很簡(jiǎn)單的,一般情況下不需要理會(huì)通道順序與字節(jié)序問(wèn)題——

// 用if分支做飽和處理
void f0_if(BYTE* pbufD, const signed short* pbufS, int cnt)
{
const signed short* pS = pbufS;
BYTE* pD = pbufD;
int i;
for(i=0; i<cnt; ++i)
{
// 分別對(duì)4個(gè)通道做飽和處理
pD[0] = (pS[0]<0) ? 0 : ( (pS[0]>255) ? 255 : (BYTE)pS[0] );
pD[1] = (pS[1]<0) ? 0 : ( (pS[1]>255) ? 255 : (BYTE)pS[1] );
pD[2] = (pS[2]<0) ? 0 : ( (pS[2]>255) ? 255 : (BYTE)pS[2] );
pD[3] = (pS[3]<0) ? 0 : ( (pS[3]>255) ? 255 : (BYTE)pS[3] );
// next
pS += 4;
pD += 4;
}
}



  參數(shù)說(shuō)明——
pbufD:目標(biāo)緩沖區(qū)的地址。如“64位像素”數(shù)組的首地址。
pbufS:源緩沖區(qū)的地址。如“32位像素”數(shù)組的首地址。
cnt:像素個(gè)數(shù)。

  使用方法——

signed short bufS[DATASIZE*4]; // 源緩沖區(qū)。64位的顏色(4通道,每通道16位)
BYTE bufD[DATASIZE*4]; // 目標(biāo)緩沖區(qū)。32位的顏色(4通道,每通道8位)

f0_if(bufD, bufS, DATASIZE);



  對(duì)于數(shù)據(jù)處理來(lái)說(shuō),用指針比用數(shù)組寫(xiě)起來(lái)更簡(jiǎn)潔,而且執(zhí)行速度更快。
  而且C語(yǔ)言中的指針支持下標(biāo)運(yùn)算符,能夠用下標(biāo)訪問(wèn)后面的元素(“pD[1]”相當(dāng)于“*(pD + 1)”),簡(jiǎn)化了不少代碼。(指針下標(biāo)可參考 http://www.lupaworld.com/home-space-uid-77885-do-blog-id-28843.html)
  例如“pD[1] = (pS[1]<0) ? 0 : ( (pS[1]>255) ? 255 : (BYTE)pS[1] );”這行代碼的數(shù)組寫(xiě)法為——

pbufD[i*4+1] = (pbufS[i*4+1]<0) ? 0 : ( (pbufS[i*4+1]>255) ? 255 : (BYTE)pbufS[i*4+1] );

?

  因?yàn)闂l件語(yǔ)句“if”的代碼寫(xiě)起來(lái)比較繁瑣,所以這里用到了條件運(yùn)算符“?:”來(lái)簡(jiǎn)化代碼。例如“pD[1] = (pS[1]<0) ? 0 : ( (pS[1]>255) ? 255 : (BYTE)pS[1] );”這行代碼的“if”寫(xiě)法為——

if (pS[1]<0)
pD[1]=0
else
if (pS[1]>255)
pD[1]=255
else
pD[1]=(BYTE)pS[1];

?


二、測(cè)試方法——測(cè)試程序的框架

  前面已經(jīng)編寫(xiě)了一個(gè)函數(shù)f0_if,隨后我們會(huì)編寫(xiě)多個(gè)函數(shù),分別測(cè)試性能。具體怎么測(cè)試呢?難道是為每一個(gè)函數(shù)都寫(xiě)一套測(cè)試代碼……不,那樣的話太糟糕了。
  我們可以利用函數(shù)指針進(jìn)行統(tǒng)一的測(cè)試。函數(shù)指針定義如下,與f0_if的參數(shù)列相同——

// 測(cè)試時(shí)的函數(shù)類(lèi)型
typedef void (*TESTPROC)(BYTE* pbufD, const signed short* pbufS, int cnt);

?

  有了函數(shù)指針后,進(jìn)行測(cè)試就很簡(jiǎn)單了,只需要將要測(cè)試的函數(shù)傳遞過(guò)去就行了。例如這樣測(cè)試f0_if——

runTest("f0_if", f0_if);

?

  runTest代碼如下——

// 進(jìn)行測(cè)試
void runTest(char* szname, TESTPROC proc)
{
int i,j;
DWORD tm0, tm1; // 存儲(chǔ)時(shí)間
for(i=1; i<=3; ++i) // 多次測(cè)試
{
//tm0 = GetTickCount();
tm0 = timeGetTime();
// main
for(j=1; j<=4000; ++j) // 重復(fù)運(yùn)算幾次延長(zhǎng)時(shí)間,避免計(jì)時(shí)精度問(wèn)題
{
proc(bufD, bufS, DATASIZE);
}
// show
//tm1 = GetTickCount() - tm0;
tm1 = timeGetTime() - tm0;
printf("%s[%d]:\t%u\n", szname, i, tm1);

}
}

?

  printf輸出的是測(cè)試時(shí)間,單位毫秒。值越小,表示所花時(shí)間越少、運(yùn)行速度越快、性能越高。
  這里用到了timeGetTime來(lái)計(jì)算時(shí)間,要注意加上winmm.lib庫(kù)——

  對(duì)于bufD、bufS、DATASIZE,我是這樣定義的——

// 數(shù)據(jù)規(guī)模
#define DATASIZE 16384 // 128KB / (sizeof(signed short) * 4)

// 緩沖區(qū)
signed short bufS[DATASIZE*4]; // 源緩沖區(qū)。64位的顏色(4通道,每通道16位)
BYTE bufD[DATASIZE*4]; // 目標(biāo)緩沖區(qū)。32位的顏色(4通道,每通道8位)

?

  緩沖區(qū)的尺寸是特意規(guī)定的。對(duì)于現(xiàn)在主流CPU來(lái)說(shuō),Intel處理器的二級(jí)緩存一般是每核心256KB,而AMD處理器的二級(jí)緩存一般是每核心512KB。所以數(shù)據(jù)最好不要超過(guò)256KB,這樣就能在二級(jí)緩存上完成處理,避免了內(nèi)存訪問(wèn)延時(shí)造成的干擾。
  于是我給bufS分配了128KB,給bufD分配了64KB。


三、更多的測(cè)試

  用min、max做飽和處理——

// 用min、max飽和處理
void f1_min(BYTE* pbufD, const signed short* pbufS, int cnt)
{
const signed short* pS = pbufS;
BYTE* pD = pbufD;
int i;
for(i=0; i<cnt; ++i)
{
// 分別對(duì)4個(gè)通道做飽和處理
pD[0] = min(max(0, pS[0]), 255);
pD[1] = min(max(0, pS[1]), 255);
pD[2] = min(max(0, pS[2]), 255);
pD[3] = min(max(0, pS[3]), 255);
// next
pS += 4;
pD += 4;
}
}

?

  用位掩碼做飽和處理,用求負(fù)生成掩碼——

// 用位掩碼做飽和處理.用求負(fù)生成掩碼
void f2_neg(BYTE* pbufD, const signed short* pbufS, int cnt)
{
const signed short* pS = pbufS;
BYTE* pD = pbufD;
int i;
for(i=0; i<cnt; ++i)
{
// 分別對(duì)4個(gè)通道做飽和處理
pD[0] = LIMITSU_BYTE(pS[0]);
pD[1] = LIMITSU_BYTE(pS[1]);
pD[2] = LIMITSU_BYTE(pS[2]);
pD[3] = LIMITSU_BYTE(pS[3]);
// next
pS += 4;
pD += 4;
}
}



  用位掩碼做飽和處理,用帶符號(hào)右移生成掩碼——

// 用位掩碼做飽和處理.用帶符號(hào)右移生成掩碼
void f3_sar(BYTE* pbufD, const signed short* pbufS, int cnt)
{
const signed short* pS = pbufS;
BYTE* pD = pbufD;
int i;
for(i=0; i<cnt; ++i)
{
// 分別對(duì)4個(gè)通道做飽和處理
pD[0] = LIMITSW_BYTE(pS[0]);
pD[1] = LIMITSW_BYTE(pS[1]);
pD[2] = LIMITSW_BYTE(pS[2]);
pD[3] = LIMITSW_BYTE(pS[3]);
// next
pS += 4;
pD += 4;
}
}

?


四、全部代碼

  全部代碼為——

// 用位掩碼做飽和處理.用求負(fù)生成掩碼
#define LIMITSU_FAST(n, bits) ( (n) & -((n) >= 0) | -((n) >= (1<<(bits))) )
#define LIMITSU_SAFE(n, bits) ( (LIMITSU_FAST(n, bits)) & ((1<<(bits)) - 1) )
#define LIMITSU_BYTE(n) ((BYTE)(LIMITSU_FAST(n, 8)))

// 用位掩碼做飽和處理.用帶符號(hào)右移生成掩碼
#define LIMITSW_FAST(n, bits) ( ( (n) | ((signed short)((1<<(bits)) - 1 - (n)) >> 15) ) & ~((signed short)(n) >> 15) )
#define LIMITSW_SAFE(n, bits) ( (LIMITSW_FAST(n, bits)) & ((1<<(bits)) - 1) )
#define LIMITSW_BYTE(n) ((BYTE)(LIMITSW_FAST(n, 8)))


// 數(shù)據(jù)規(guī)模
#define DATASIZE 16384 // 128KB / (sizeof(signed short) * 4)

// 緩沖區(qū)
signed short bufS[DATASIZE*4]; // 源緩沖區(qū)。64位的顏色(4通道,每通道16位)
BYTE bufD[DATASIZE*4]; // 目標(biāo)緩沖區(qū)。32位的顏色(4通道,每通道8位)

// 測(cè)試時(shí)的函數(shù)類(lèi)型
typedef void (*TESTPROC)(BYTE* pbufD, const signed short* pbufS, int cnt);

// 用if分支做飽和處理
void f0_if(BYTE* pbufD, const signed short* pbufS, int cnt)
{
const signed short* pS = pbufS;
BYTE* pD = pbufD;
int i;
for(i=0; i<cnt; ++i)
{
// 分別對(duì)4個(gè)通道做飽和處理
pD[0] = (pS[0]<0) ? 0 : ( (pS[0]>255) ? 255 : (BYTE)pS[0] );
pD[1] = (pS[1]<0) ? 0 : ( (pS[1]>255) ? 255 : (BYTE)pS[1] );
pD[2] = (pS[2]<0) ? 0 : ( (pS[2]>255) ? 255 : (BYTE)pS[2] );
pD[3] = (pS[3]<0) ? 0 : ( (pS[3]>255) ? 255 : (BYTE)pS[3] );
// next
pS += 4;
pD += 4;
}
}

// 用min、max飽和處理
void f1_min(BYTE* pbufD, const signed short* pbufS, int cnt)
{
const signed short* pS = pbufS;
BYTE* pD = pbufD;
int i;
for(i=0; i<cnt; ++i)
{
// 分別對(duì)4個(gè)通道做飽和處理
pD[0] = min(max(0, pS[0]), 255);
pD[1] = min(max(0, pS[1]), 255);
pD[2] = min(max(0, pS[2]), 255);
pD[3] = min(max(0, pS[3]), 255);
// next
pS += 4;
pD += 4;
}
}

// 用位掩碼做飽和處理.用求負(fù)生成掩碼
void f2_neg(BYTE* pbufD, const signed short* pbufS, int cnt)
{
const signed short* pS = pbufS;
BYTE* pD = pbufD;
int i;
for(i=0; i<cnt; ++i)
{
// 分別對(duì)4個(gè)通道做飽和處理
pD[0] = LIMITSU_BYTE(pS[0]);
pD[1] = LIMITSU_BYTE(pS[1]);
pD[2] = LIMITSU_BYTE(pS[2]);
pD[3] = LIMITSU_BYTE(pS[3]);
// next
pS += 4;
pD += 4;
}
}

// 用位掩碼做飽和處理.用帶符號(hào)右移生成掩碼
void f3_sar(BYTE* pbufD, const signed short* pbufS, int cnt)
{
const signed short* pS = pbufS;
BYTE* pD = pbufD;
int i;
for(i=0; i<cnt; ++i)
{
// 分別對(duì)4個(gè)通道做飽和處理
pD[0] = LIMITSW_BYTE(pS[0]);
pD[1] = LIMITSW_BYTE(pS[1]);
pD[2] = LIMITSW_BYTE(pS[2]);
pD[3] = LIMITSW_BYTE(pS[3]);
// next
pS += 4;
pD += 4;
}
}

// 進(jìn)行測(cè)試
void runTest(char* szname, TESTPROC proc)
{
int i,j;
DWORD tm0, tm1; // 存儲(chǔ)時(shí)間
for(i=1; i<=3; ++i) // 多次測(cè)試
{
//tm0 = GetTickCount();
tm0 = timeGetTime();
// main
for(j=1; j<=4000; ++j) // 重復(fù)運(yùn)算幾次延長(zhǎng)時(shí)間,避免計(jì)時(shí)精度問(wèn)題
{
proc(bufD, bufS, DATASIZE);
}
// show
//tm1 = GetTickCount() - tm0;
tm1 = timeGetTime() - tm0;
printf("%s[%d]:\t%u\n", szname, i, tm1);

}
}

int main(int argc, char* argv[])
{
int i; // 循環(huán)變量

//printf("Hello World!\n");
printf("== noif:VC6 ==");

// 初始化
srand( (unsigned)time( NULL ) );
for(i=0; i<DATASIZE*4; ++i)
{
bufS[i] = (signed short)((rand()&0x1FF) - 128); // 使數(shù)值在 [-128, 383] 區(qū)間
}

// 準(zhǔn)備開(kāi)始??梢詫⑦M(jìn)程優(yōu)先級(jí)設(shè)為實(shí)時(shí)
if (argc<=1)
{
printf("<Press any key to continue>");
getch();
printf("\n");
}

// 進(jìn)行測(cè)試
runTest("f0_if", f0_if);
runTest("f1_min", f1_min);
runTest("f2_neg", f2_neg);
runTest("f3_sar", f3_sar);

// 結(jié)束前提示
if (argc<=1)
{
printf("<Press any key to exit>");
getch();
printf("\n");
}

return 0;
}

?


五、測(cè)試結(jié)果

  將程序編譯為“Release”版,然后分別在不同的系統(tǒng)環(huán)境中進(jìn)行測(cè)試。

  在32位winXP上的測(cè)試結(jié)果——

== noif:VC6 ==<Press any key to continue>
f0_if[1]: 2016
f0_if[2]: 2016
f0_if[3]: 2015
f1_min[1]: 2063
f1_min[2]: 2062
f1_min[3]: 2063
f2_neg[1]: 718
f2_neg[2]: 719
f2_neg[3]: 719
f3_sar[1]: 672
f3_sar[2]: 687
f3_sar[3]: 672

?

  在64位win7上的測(cè)試結(jié)果——

== noif:VC6 ==<Press any key to continue>
f0_if[1]: 2075
f0_if[2]: 2012
f0_if[3]: 2028
f1_min[1]: 2059
f1_min[2]: 2075
f1_min[3]: 2075
f2_neg[1]: 717
f2_neg[2]: 718
f2_neg[3]: 718
f3_sar[1]: 670
f3_sar[2]: 687
f3_sar[3]: 686

?

  硬件環(huán)境——
CPU:Intel Core i3-2310M, 2100 MHz
內(nèi)存:DDR3-1066


源碼下載——
http://files.cnblogs.com/zyl910/noifVC6.rar
(建議閱讀編譯器生成的匯編代碼,位于Release\noifVC6.asm )

?

轉(zhuǎn)載于:https://www.cnblogs.com/zyl910/archive/2012/03/27/noifopex3.html

總結(jié)

以上是生活随笔為你收集整理的深入探讨用位掩码代替分支(3):VC6速度测试的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。