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]=0elseif (pS[1]>255)        pD[1]=255else        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]:       2016f0_if[2]:       2016f0_if[3]:       2015f1_min[1]:      2063f1_min[2]:      2062f1_min[3]:      2063f2_neg[1]:      718f2_neg[2]:      719f2_neg[3]:      719f3_sar[1]:      672f3_sar[2]:      687f3_sar[3]:      672

  在64位win7上的测试结果——

== noif:VC6 ==<Press any key to continue>f0_if[1]:       2075f0_if[2]:       2012f0_if[3]:       2028f1_min[1]:      2059f1_min[2]:      2075f1_min[3]:      2075f2_neg[1]:      717f2_neg[2]:      718f2_neg[3]:      718f3_sar[1]:      670f3_sar[2]:      687f3_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

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

  1. 深入探讨用位掩码代替分支(8):SSE指令集速度测试

    在上一篇测试了MMX指令集,这次我们来测试SSE指令集.说的更精确一点,是测试SSE2指令集. 本篇致力于解决以下问题-- 1.SSE/SSE2指令集是什么? 2.如何阅读Intel/AMD的手册? ...

  2. 深入探讨用位掩码代替分支(6):VB6速度测试

    前面我们测试了C系列语言,验证了位掩码算法的确实性能不错.那么对于Basic系列语言,该算法的效率怎样呢?于是本文对此进行探讨. VB.Net与C#一样,也是由.Net虚拟机执行的,没有多大的测试价值 ...

  3. 深入探讨用位掩码代替分支(7):MMX指令集速度测试

    前面我们测试了高级语言做饱和处理的性能.其实,对于这样的大批量数据处理,使用SIMD(Single Instruction Multiple Data,单指令多数据流)技术能极大的提高性能.MMX指令 ...

  4. 位掩码(bitmask)在windows 串口事件驱动中的应用

    最近在学习Windows串口通信,以事件驱动模式开发相关应用时,肯定会用到以下几个函数: SetCommMask(HANDLE hComm, DWORD dwEvtMask); GetCommMask ...

  5. java 位掩码_Java位掩码控制权限与()或(|)非(~)、的介绍

    1. java 位掩码 java 位掩码,在java开发中很少有场景会用到掩码,但是当系统中需要判断某个对象是否有 某些权限时,可以通过位掩码来做. 位掩码 主要通过位运算,例如与(&).非( ...

  6. Java位运算在程序设计中的使用:位掩码(BitMask)

    2019独角兽企业重金招聘Python工程师标准>>> Permission public class Permission {// 是否允许查询private boolean al ...

  7. 位掩码(BitMask)

    位运算在实际开发中用得很少,主要原因还是它对于不熟悉的人不好读不好懂不好计算,如果不经常实践会生疏.但它的优点自然是计算快,代码更少.在某些地方它的优势会更加明显比如如下代码(http://xxgbl ...

  8. ipv6的127位掩码如何表示_子网掩码

    子网掩码(subnet mask)又叫网络掩码.地址掩码.子网络遮罩,它是一种用来指明一个IP地址的哪些位标识的是主机所在的子网,以及哪些位标识的是主机的位掩码.子网掩码不能单独存在,它必须结合IP地 ...

  9. php获取掩码,如何在PHP中实现位掩码?

    这很简单.首先一点代码演示如何实现.如果你不明白这个代码是做什么或它的工作原理,请随时在评论中提出其他问题: const FLAG_1 = 0b0001; // 1 const FLAG_2 = 0b ...

最新文章

  1. Sg.js框架核心概念
  2. 安装RADOS gateway(CEPH对象存储)
  3. 监控Linux服务器网站状态的SHELL脚本
  4. 一道时间复杂度的题目
  5. 计算机移动设备有限公司,使计算机,移动设备更节能
  6. 云网管—云上构建网络自动化体系
  7. 如何进行产品战略规划
  8. 深度学习笔记_基本概念_梯度下降及示例代码
  9. 微信小程序 java家庭个人收支理财记账本springboot
  10. 机器学习sklearn(13)层次聚类
  11. Pyecharts树状图:树图
  12. uniapp实现公众号H5、小程序和App微信授权登录功能
  13. 联想拯救者wif开不了_联想拯救者wifi开关
  14. python function gamma_Python math gamma()用法及代码示例
  15. 5gh掌上云计算认证不通过_华为云计算认证含金量怎么样?
  16. 3种方法保护Word文档不可随意编辑
  17. 编译原理课堂笔记(1)编译概述
  18. 分享网页文章到微信时如何自定义缩略图、链接、标题和摘要
  19. 色彩学基础知识(转)
  20. 法拉第效应维尔德常数_什么是法拉第效应?

热门文章

  1. 两个人投票的c语言程序,设计网页投票器(二)《精通Unix下C语言编程与项目实践》之十...
  2. java向指定文件继续写内容_java 向指定文件写入内容(如文件存在,则先删除再创建;写入如目录不存在,则创建)...
  3. c语言输出星期几的英语表达,C语言程序设计: 输入年月日 然后输出是星期几...
  4. windows系统git服务器启动,windowsServer服务器上搭建GIt服务器
  5. mysql函数封装_PHP访问MYSQL数据库封装类(附函数说明)
  6. 工业POE供电交换机在安防应用中的优势有哪些?
  7. 什么是模拟量光端机?模拟光端机品牌有哪些?
  8. 如何分辨PoE工业交换机是否标准供电
  9. 国家开放大学2021春1032成本管理题目
  10. 常用计算机网络性能指标的是什么,什么是Bit?【计算机网络的性能指标】