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

  wuhanbingwhdx提到了数据相关也会影响流水线(http://blog.csdn.net/zyl910/article/details/1330614)。
  他的说法是有一定道理的。但是,在很多时候我们并不仅仅处理一个数值。比如将循环展开,在内循环处理2个或更多个的数值。而现代编译器面对循环展开时,在编译优化操作中会调整指令顺序,错开有相关性指令。因现代处理器支持超标量,这样的指令顺序调整能获得较好的指令级并行度,从而优化了性能。
  其次,就算编译器对循环展开优化的不够彻底,没将相关性指令错开。但因现代处理器支持乱序执行,当遇到相关性指令需要等待时,处理器会处理后面未相关的指令,从而保持处理器满载尽量减轻相关性等待造成的性能损失。
  第三,现代处理器还支持寄存器重命名技术——当两处代码用到同名的寄存器时,译码器会做寄存器重命名处理给它们分配不同的寄存器,使数据不会干扰,从而获得更高的指令级并行度。

  上面说了很多理论知识,实际性能到底怎么样呢?还是写代码测一测吧。


一、目标——将64位像素转为32位像素

  将64位像素转为32位像素,是饱和处理的最典型应用。
  64位像素有4个通道,每个通道16位(带符号16位整数)。每像素8个字节。
  32位像素有4个通道,每个通道8位(无符号8位整数)。每像素4个字节。

  转换方法为——将每个像素的4个通道由16位(带符号16位整数)转为8位(无符号8位整数)。因为每次都是对4个通道进行处理,所以能获得较高的指令级并行度。

  具体的存储格式为——

注:
1.内存地址由低到高(从下到上),垂直方向的每一格是一个字节。+0代表数据基址,+1代表数据基址+1,以此类推。
2.左为“64位像素”数组,右为“32位像素”数组。
3.图中使用了用双边线来分隔像素。因“64位像素”是8字节,而“32位像素”是4字节。所以对于16个字节的空间,左侧能存放2个像素、右侧能存放4个像素。
4.图中使用了用实线来分隔通道。“64位像素”的通道是16位,占用2个字节。“32位像素”的通道是8位,只占用1个字节。
5.图中使用了用虚线来分隔字节。主要用于“64位像素”。
6.这里采用了Windows位图通道规则,即通道顺序为B、G、R、A(从低到高)。例如:“B0”代表像素0的B(蓝色)通道、“A0”代表像素0的A(不透明度)通道、“A1”代表像素1的A通道……以此类推。
7.这里采用了小端(Little Endian)方式的字节序(Endianness),即最低地址存放的最低字节。采用小写的“h”、“l”来表示“64位像素”通道的高、低字节。例如:“B0l”代表像素0的B通道的低字节、“A0h”代表像素0的A通道的高字节……以此类推。

  上面貌似挺复杂,又是图表又是大段文字的。其实,代码写起来很简单的,一般情况下不需要理会通道顺序与字节序问题——

// 用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)
{
// 分别对4个通道做饱和处理
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;
}
}



  参数说明——
pbufD:目标缓冲区的地址。如“64位像素”数组的首地址。
pbufS:源缓冲区的地址。如“32位像素”数组的首地址。
cnt:像素个数。

  使用方法——

signed short    bufS[DATASIZE*4];    // 源缓冲区。64位的颜色(4通道,每通道16位)
BYTE bufD[DATASIZE*4]; // 目标缓冲区。32位的颜色(4通道,每通道8位)

f0_if(bufD, bufS, DATASIZE);



  对于数据处理来说,用指针比用数组写起来更简洁,而且执行速度更快。
  而且C语言中的指针支持下标运算符,能够用下标访问后面的元素(“pD[1]”相当于“*(pD + 1)”),简化了不少代码。(指针下标可参考 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] );”这行代码的数组写法为——

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

 

  因为条件语句“if”的代码写起来比较繁琐,所以这里用到了条件运算符“?:”来简化代码。例如“pD[1] = (pS[1]<0) ? 0 : ( (pS[1]>255) ? 255 : (BYTE)pS[1] );”这行代码的“if”写法为——

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

 


二、测试方法——测试程序的框架

  前面已经编写了一个函数f0_if,随后我们会编写多个函数,分别测试性能。具体怎么测试呢?难道是为每一个函数都写一套测试代码……不,那样的话太糟糕了。
  我们可以利用函数指针进行统一的测试。函数指针定义如下,与f0_if的参数列相同——

// 测试时的函数类型
typedef void (*TESTPROC)(BYTE* pbufD, const signed short* pbufS, int cnt);

 

  有了函数指针后,进行测试就很简单了,只需要将要测试的函数传递过去就行了。例如这样测试f0_if——

runTest("f0_if", f0_if);

 

  runTest代码如下——

// 进行测试
void runTest(char* szname, TESTPROC proc)
{
int i,j;
DWORD tm0, tm1; // 存储时间
for(i=1; i<=3; ++i) // 多次测试
{
//tm0 = GetTickCount();
tm0 = timeGetTime();
// main
for(j=1; j<=4000; ++j) // 重复运算几次延长时间,避免计时精度问题
{
proc(bufD, bufS, DATASIZE);
}
// show
//tm1 = GetTickCount() - tm0;
tm1 = timeGetTime() - tm0;
printf("%s[%d]:\t%u\n", szname, i, tm1);

}
}

 

  printf输出的是测试时间,单位毫秒。值越小,表示所花时间越少、运行速度越快、性能越高。
  这里用到了timeGetTime来计算时间,要注意加上winmm.lib库——

  对于bufD、bufS、DATASIZE,我是这样定义的——

// 数据规模
#define DATASIZE 16384 // 128KB / (sizeof(signed short) * 4)

// 缓冲区
signed short bufS[DATASIZE*4]; // 源缓冲区。64位的颜色(4通道,每通道16位)
BYTE bufD[DATASIZE*4]; // 目标缓冲区。32位的颜色(4通道,每通道8位)

 

  缓冲区的尺寸是特意规定的。对于现在主流CPU来说,Intel处理器的二级缓存一般是每核心256KB,而AMD处理器的二级缓存一般是每核心512KB。所以数据最好不要超过256KB,这样就能在二级缓存上完成处理,避免了内存访问延时造成的干扰。
  于是我给bufS分配了128KB,给bufD分配了64KB。


三、更多的测试

  用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)
{
// 分别对4个通道做饱和处理
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;
}
}

 

  用位掩码做饱和处理,用求负生成掩码——

// 用位掩码做饱和处理.用求负生成掩码
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)
{
// 分别对4个通道做饱和处理
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;
}
}



  用位掩码做饱和处理,用带符号右移生成掩码——

// 用位掩码做饱和处理.用带符号右移生成掩码
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)
{
// 分别对4个通道做饱和处理
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;
}
}

 


四、全部代码

  全部代码为——

// 用位掩码做饱和处理.用求负生成掩码
#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)))

// 用位掩码做饱和处理.用带符号右移生成掩码
#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)))


// 数据规模
#define DATASIZE 16384 // 128KB / (sizeof(signed short) * 4)

// 缓冲区
signed short bufS[DATASIZE*4]; // 源缓冲区。64位的颜色(4通道,每通道16位)
BYTE bufD[DATASIZE*4]; // 目标缓冲区。32位的颜色(4通道,每通道8位)

// 测试时的函数类型
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)
{
// 分别对4个通道做饱和处理
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)
{
// 分别对4个通道做饱和处理
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;
}
}

// 用位掩码做饱和处理.用求负生成掩码
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)
{
// 分别对4个通道做饱和处理
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;
}
}

// 用位掩码做饱和处理.用带符号右移生成掩码
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)
{
// 分别对4个通道做饱和处理
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;
}
}

// 进行测试
void runTest(char* szname, TESTPROC proc)
{
int i,j;
DWORD tm0, tm1; // 存储时间
for(i=1; i<=3; ++i) // 多次测试
{
//tm0 = GetTickCount();
tm0 = timeGetTime();
// main
for(j=1; j<=4000; ++j) // 重复运算几次延长时间,避免计时精度问题
{
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; // 循环变量

//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); // 使数值在 [-128, 383] 区间
}

// 准备开始。可以将将进程优先级设为实时
if (argc<=1)
{
printf("<Press any key to continue>");
getch();
printf("\n");
}

// 进行测试
runTest("f0_if", f0_if);
runTest("f1_min", f1_min);
runTest("f2_neg", f2_neg);
runTest("f3_sar", f3_sar);

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

return 0;
}

 


五、测试结果

  将程序编译为“Release”版,然后分别在不同的系统环境中进行测试。

  在32位winXP上的测试结果——

== 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上的测试结果——

== 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

 

  硬件环境——
CPU:Intel Core i3-2310M, 2100 MHz
内存:DDR3-1066


源码下载——
http://files.cnblogs.com/zyl910/noifVC6.rar
(建议阅读编译器生成的汇编代码,位于Release\noifVC6.asm )

 

转载于:https://www.cnblogs.com/zyl910/archive/2012/03/27/noifopex3.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/361574.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Web安全相关(三):开放重定向(Open Redirection)

简介 那些通过请求&#xff08;如查询字符串和表单数据&#xff09;指定重定向URL的Web程序可能会被篡改&#xff0c;而把用户重定向到外部的恶意URL。这种篡改就被称为开发重定向攻击。 场景分析 假设有一个正规网站http://nerddinner.com/&#xff0c;还有一个恶意网站或钓鱼…

自我修复的JVM

这篇帖子是关于一个应用程序的示例&#xff0c;其中解决每个IT问题的第一个解决方案&#xff08;“您尝试过关闭并重新打开它”&#xff09;可能适得其反&#xff0c;弊大于利。 我们没有关闭或重新打开设备的方法&#xff0c;而是拥有一个可以自愈的应用程序&#xff1a;它在…

微信小程序实战–集阅读与电影于一体的小程序项目(八)

31.电影详情页面 movie-template.wxml <view class"movie-container" catchtap"onMovieTap" data-movieId"{{movieId}}"> movie.js onMovieTap:function(event) {var movieId event.currentTarget.dataset.movieid;wx.navigateTo({url…

Resource接口

【转】https://blog.csdn.net/hbtj_1216/article/details/85487787 参考&#xff1a;官方文档 1 简介 Java标准库中的java.net.URL类和标准处理器对于处理低层的资源没有提供很好的功能。例如&#xff0c;并没有提供一个URL的实现能够从classpath或者ServletContext中读取资源等…

JPA实体图

JPA 2.1的最新功能之一是能够使用实体图指定获取计划。 这很有用&#xff0c;因为它允许您自定义使用查询或查找操作检索的数据。 当使用中型到大型应用程序时&#xff0c;通常以不同的方式显示来自同一实体的数据。 在其他情况下&#xff0c;您只想选择最小的信息集即可优化应…

微信小程序实战–集阅读与电影于一体的小程序项目(三)

14.wx.showToast交互反馈 wx.showToast文档 post-detail.js添加个消息提示框 onCollectionTap: function(ev) {var postsCollected wx.getStorageSync(posts_Collected)var postCollected postsCollected[this.data.currentPostId]postCollected !postCollected;postsCol…

Java SE 8新功能介绍:Lambda的遍历,过滤,处理集合和方法增强

在“ Java SE 8新功能导览”系列的这篇文章中&#xff0c;我们将深入解释并探索代码&#xff0c;以了解如何使用lambda表达式和方法引用 遍历集合 &#xff0c;并使用谓词接口过滤它们&#xff0c;实现默认方法在接口中&#xff0c;最后在接口中实现静态方法 。 在上一篇文章“…

C#锐利体验-第八讲 索引器与操作符重载(转)

第八讲 索引器与操作符重载 南京邮电学院 李建忠&#xff08;cornyfield263.net&#xff09; 索引 C#锐利体验 "Hello,World&#xff01;"程序C#语言基础介绍Microsoft.NET平台基础构造类与对象 构造器与析构器方法域与属性索引器与操作符重载 数组与字符串特征与映射…

Java EE 7 / JAX-RS 2.0:具有自定义HTTP标头的简单REST API身份验证和授权

在使用已可用的HTTP协议实施Web服务时&#xff0c;REST带来了很多便利。 通过仅通过指定的URL触发GET&#xff0c;POST和其他HTTP方法&#xff0c;您将确保通过REST服务的响应来完成某些工作。 但是&#xff0c;无论REST给开发人员带来了什么便利&#xff0c;安全性和访问控制的…

easyui树形菜单实现

需求&#xff1a;读取路径配置中的相对路径获取对应的子文件夹及其子文件并形成树形结构&#xff0c;加载xml文件&#xff0c;输入搜索关键字匹配xml里面的value节点的值对应的contact值的集合并进行搜索 例如&#xff1a;输入b&#xff0c;找到xml里面的文本节点等于b的value…

fatal error C1083: 无法打开预编译头文件:“Debug\a.pch”:No such file or directory

一、解决方法 右键点击你创建的项目&#xff0c;选择“属性标签”点击属性&#xff0c;弹出“项目属性页”&#xff0c;在左侧找到以下位置 配置属性 --> C/C --> 预编译头&#xff0c;并选择它&#xff1a;在右边的菜单中选择 “创建/使用预编译头”中的“不使用预编…

Telnet初试(本地测试)

win7下开启Telnet功能&#xff1a; 控制面板-程序和功能- 开启服务 然后回车 这样即可完成一次请求 更多专业前端知识&#xff0c;请上 【猿2048】www.mk2048.com

您是否真的要加快Maven的编译/打包速度? 那么takari生命周期插件就是答案。

像你们中的许多人一样&#xff0c;我正在使用多模块Maven项目 。 与现有的许多系统相比&#xff0c;这不是一个很大的数目&#xff0c;它具有15个模块&#xff0c;3种不同的耳朵部署&#xff0c;带有属性文件的大量参数化以及大约10万行Java代码。 在开发高峰期&#xff0c;由于…

手机MMI体系结构及其实现

摘自&#xff1a;http://blog.csdn.net/zc2007/article/details/2340436 1引言 MMI&#xff08;ManMachineInter-face&#xff09;&#xff0c;即人机界面&#xff0c;它负责和用户的交互&#xff0c;在必要的时候调用其它模块的功能。MMI模块在整个系统中处于最 高层&#x…

Aspose.Words简单生成word文档

Aspose.Words简单生成word文档 Aspose.Words.Document doc new Aspose.Words.Document(); Aspose.Words.DocumentBuilder builder new Aspose.W…

ubuntu下安装JDK和netbeans

我在ubuntu下安装netbeans十分简单&#xff0c;我下载了jdk-7u1-nb-7_0_1-linux-ml.sh&#xff0c;直接在终端输入 sh jdk-7u1-nb-7_0_1-linux-ml.sh安装的向导就会启动&#xff0c;你只要选择JDK和netbeans安装的目录&#xff0c;向导就自动替你安装jdk和netbeans&#xff0c;…

在带有组合框的值列表的下拉列表中显示显示属性的子集

组合框值列表&#xff08;inputComboboxListOfValues&#xff09;应该是使用LOV的非常流行的ADF Faces组件。 坦白说&#xff0c;这是我最喜欢的值列表方法。 在这篇简短的文章中&#xff0c;我将重点介绍ADF开发人员经常忽略的一项功能。 如果默认情况下定义了LOV&#xff0c;…

具有WildFly,Arquillian,Jenkins和OpenShift的Java EE 7部署管道

技术提示&#xff03;54展示了如何Arquillianate&#xff08;Arquillianize&#xff1f;&#xff09;一个现有的Java EE项目并在WildFly在已知主机和端口上运行的远程模式下运行这些测试。 技术提示&#xff03;55展示了当WildFly在OpenShift中运行时如何运行这些测试。 这两个…

css浮动(float)及清除浮动的几种实用方法

CSS浮动是现在网页布局中使用最频繁的效果之一,而浮动可以帮我们解决很多问题,那么就让我们一起来看一看如何使用浮动. 一.css浮动(float) (1)html文档流 自窗体自上而下分成一行一行&#xff0c;并在每行中按从左到右的顺序排放元素。 (2)网页中大部分对象默认是占用文档流…

一台电脑同时添加git和bitbucket两个网站的ssh key

添加第一个ssh key 就不多说了&#xff0c;不懂的可以自己查资料 ssh-keygen -t rsa -C email_1email.com 然后一路enter就好了 假设已经添加好了git的ssh key 。现在要添加bitbucket的ssh key 首先 ssh-keygen -t rsa -C email_2email.com //同一个邮箱也可以 然后指定公钥的…