zoukankan      html  css  js  c++  java
  • 深入探讨用位掩码代替分支(5):C#2010速度测试

      前面我们测试了C语言这样纯编译型语言。那么对于像C#这样由虚拟机执行的语言,“位掩码代替分支”法是否也有效果呢?于是本文对此进行探讨。

    一、移植要点

      C#的语法与C语言很相像,多数代码可以直接用,但要注意以下几点。

    1.1 unsafe——不安全上下文

      C#虽然支持指针,但必须在unsafe——不安全上下文 中才能使用。

      要使用unsafe,必须先配置项目属性,允许不安全代码——
    1.项目->属性,打开项目的属性页。
    2.点击左侧“生成”,切换到“生成”页。
    3.点击上侧的“配置”组合框,选择“所有配置(C)”。
    4.勾选“常规”中的“允许不安全代码(F)”。
    5.点击工具栏上的“保存”按钮。

      unsafe有两种用法——
    1.unsafe方法。在函数的声明中增加“unsafe”关键字。
    2.unsafe区块。在函数内,使用“unsafe{ ... }”标识不安全代码的区块。

      当函数有指针参数时,只能使用第1种方法。
      例如,C语言中f0_if函数的声明为——

    void f0_if(BYTE* pbufD, const signed short* pbufS, int cnt)



      而在C#中,该函数的声明应改为——

    static unsafe void f0_if(byte* pbufD, short* pbufS, int cnt)



      因为f0_if不会修改对象成员,所以应加上“static”关键字表示它是静态函数,而不是对象的成员方法。


    1.2 unchecked——不检查整数溢出

      在C语言中不会检查整数溢出,正好被我们的位运算算法所利用。而在C#中,可以配置整数溢出检查的,具体配置方法为——
    1.项目->属性,打开项目的属性页。
    2.点击左侧“生成”,切换到“生成”页。
    3.将滚动条拉到最下面,点击“输出”中的“高级”,打开“高级生成设置”对话框。
    4.在“常规”中有一个“检查运算上溢/下溢(K)”。

      若配置了“检查运算上溢/下溢(K)”,当整数溢出时会抛出异常,打断程序流程。这对我们的位运算算法来说是不利的。怎么办呢?
      应使用unchecked关键字来取消整数溢出检查。
      unchecked关键字有两种用法,一种是作为区块,一种是作为函数。
      在我们的测试函数中有大量的整数运算,使用unchecked区块更加方便。


    1.3 类型检查更严格

      C#的类型检查比C语言更加严格。所以得增加更多的强制类型转换,否则编译器会发出警告甚至报错。

      例如这一行C语言代码——

    pD[0] = (pS[0]<0) ? 0 : ( (pS[0]>255) ? 255 : (BYTE)pS[0] );



      在C#中,得写成——

    pD[0] = (pS[0] < 0) ? (byte)0 : ((pS[0] > 255) ? (byte)255 : (byte)pS[0]);



      结合以上3点,将f0_if函数转为C#语法——

            // 用if分支做饱和处理
    static unsafe void f0_if(byte* pbufD, short* pbufS, int cnt)
    {
    short* pS = pbufS;
    byte* pD = pbufD;
    int i;
    for (i = 0; i < cnt; ++i)
    {
    // 分别对4个通道做饱和处理
    pD[0] = (pS[0] < 0) ? (byte)0 : ((pS[0] > 255) ? (byte)255 : (byte)pS[0]);
    pD[1] = (pS[1] < 0) ? (byte)0 : ((pS[1] > 255) ? (byte)255 : (byte)pS[1]);
    pD[2] = (pS[2] < 0) ? (byte)0 : ((pS[2] > 255) ? (byte)255 : (byte)pS[2]);
    pD[3] = (pS[3] < 0) ? (byte)0 : ((pS[3] > 255) ? (byte)255 : (byte)pS[3]);
    // next
    pS += 4;
    pD += 4;
    }
    }


      其他测试函数也可以这样处理。


    1.4 C#中不支持宏

      在C#中不支持宏,所以在移植f2_neg、f3_sar函数时,得手动实现代码。

      虽然可以将LIMITSU_BYTE这些宏改写为函数,但函数调用开销会影响性能。于是增加了f2_negB、f3_sarB这两个测试函数来验证这一论断。


    1.5 bool转整数

      在C语言中,逻辑比较的结果是整型,这样有助于位运算算法。
      但在C#,单独设计了bool类型,与整型隔离。甚至不允许强制转换,只能用Convert.ToInt16方法将bool转整数。

      例如将f2_neg函数转为C#语法——

            // 用位掩码做饱和处理.用带符号右移生成掩码
    static unsafe void f2_neg(byte* pbufD, short* pbufS, int cnt)
    {
    unchecked
    {
    short* pS = pbufS;
    byte* pD = pbufD;
    int i;
    for (i = 0; i < cnt; ++i)
    {
    // 分别对4个通道做饱和处理
    pD[0] = (byte)(pS[0] & -Convert.ToInt16(pS[0] >= 0) | -Convert.ToInt16(pS[0] >= 256));
    pD[1] = (byte)(pS[1] & -Convert.ToInt16(pS[1] >= 0) | -Convert.ToInt16(pS[1] >= 256));
    pD[2] = (byte)(pS[2] & -Convert.ToInt16(pS[2] >= 0) | -Convert.ToInt16(pS[2] >= 256));
    pD[3] = (byte)(pS[3] & -Convert.ToInt16(pS[3] >= 0) | -Convert.ToInt16(pS[3] >= 256));
    // next
    pS += 4;
    pD += 4;
    }
    }
    }



       Convert.ToInt16会带来函数调用开销,影响性能。而且,貌似它内部是采用分支实现bool到整数的转换的,又带了跳转堵塞流水线的问题。


    1.6 delegate——委托

      虽然C#不支持函数指针,但可以使用委托。
      C语言中,我们是这样定义测试函数的类型的——

    typedef void (*TESTPROC)(BYTE* pbufD, const signed short* pbufS, int cnt);



      在C#中改为委托——

    unsafe delegate void TESTPROC(byte* pbufD, short* pbufS, int cnt);



      因含有指针参数,所以得加上unsafe关键字。而且必须处于unsafe区块才能使用该委托,例如——

                // 进行测试
    unsafe
    {
    runTest("f0_if", f0_if);
    runTest("f1_min", f1_min);
    runTest("f2_neg", f2_neg);
    runTest("f2_negB", f2_negB);
    runTest("f3_sar", f3_sar);
    runTest("f3_sarB", f3_sarB);
    }




    1.7 fixed——绑定指针

      在C#中,不能像C语言那样随时可以用&运算符获取地址。得使用fixed语句来绑定变量、获取指针。MSDN对它的说明——
    http://msdn.microsoft.com/zh-cn/library/f58wzh21.aspx
    fixed 语句禁止垃圾回收器重定位可移动的变量。 fixed 语句只在不安全的上下文中是允许的。 Fixed 还可用于创建固定大小缓冲区。
    fixed 语句设置指向托管变量的指针,并在执行该语句期间“固定”此变量。 如果没有 fixed 语句,则指向可移动托管变量的指针的作用很小,因为垃圾回收可能不可预知地重定位变量。 C# 编译器只允许在 fixed 语句中分配指向托管变量的指针。

      在这里,我们利用fixed语句来获得bufS、bufD这两个缓冲区的指针。注意它们类型不同,所以得嵌套使用fixed语句。
      将f2_neg函数转为C#语法——

            // 进行测试
    static unsafe void runTest(string szname, TESTPROC proc)
    {
    int i, j;
    Stopwatch stw = new Stopwatch(); // 执行时间
    // 绑定指针
    fixed (short* pbufS = &bufS[0])
    {
    fixed (byte* pbufD = &bufD[0])
    {
    // 开始测试
    for (i = 1; i <= 3; ++i) // 多次测试
    {
    stw.Reset();
    stw.Start();
    // main
    for (j = 1; j <= 4000; ++j) // 重复运算几次延长时间,避免计时精度问题
    {
    proc(pbufD, pbufS, DATASIZE);
    }
    // show
    stw.Stop();
    //_tprintf(_T("%s[%d]:\t%u\n"), szname, i, tm1);
    Console.WriteLine("{0}[{1}]:\t{2}", szname, i, stw.ElapsedMilliseconds);

    }
    }
    }

    }




    1.8 平台配置

      在VS2010中,C#支持多种平台。本文将测试其中三种——Any、x86、x64。
      点击菜单栏的 生成->配置管理器,打开“配置管理器”对话框。C#的默认平台是“x86”,新建平台“Any”、“x64”。

      还要注意配置好输出路径,别发生冲突。
    1.项目->属性,打开项目的属性页。
    2.点击左侧“生成”,切换到“生成”页。
    3.点击上侧的“配置”组合框,切换配置。
    4.点击上侧的“平台”组合框,切换平台。
    5.修改“输出”中的“输出路径”。
    6.重复第3步至第5步,直到配置好所有平台和配置。
    7.点击工具栏上的“保存”按钮。

      建议将输出路径配置为——
    Any:bin\Debug\,bin\Release\
    x86:bin\x86\Debug\,bin\x86\Release\
    x64:bin\x64\Debug\,bin\x64\Release\

      请参考——
    http://msdn.microsoft.com/zh-cn/library/ms185328.aspx
    如何:配置项目以面向目标平台


    1.9 平台判断

      虽然C#中提供了Environment.Is64BitProcess来判断WOW64。但是现在有3种平台,该方法不适合。 这时最好使用条件编译来做平台判断。
      先在项目属性页中配置条件编译符号——
    Any:(无)
    x86:ISX86
    x64:ISX64

      然后在代码中使用条件编译语句——

    #if (ISX86)
    string sBitCode = "x86";
    #elif (ISX64)
    string sBitCode = "x64";
    #else
    string sBitCode = "any";
    #endif
    Console.Write("== noif:C#2010({0}) on {1}bit ==", sBitCode, nBitSys);




    二、全部代码

      全部代码——

    using System;
    //using System.Collections.Generic;
    //using System.Linq;
    //using System.Text;
    using System.Diagnostics;

    namespace noifCS2010
    {
    /// <summary>
    /// 用位掩码代替分支:C#2010性能测试
    /// </summary>
    /// <remarks>Author: zyl910</remarks>
    class CnoifCS2010
    {
    const int DATASIZE = 16384; // 128KB / (sizeof(signed short) * 4)

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

    unsafe delegate void TESTPROC(byte* pbufD, short* pbufS, int cnt);

    // 用if分支做饱和处理
    static unsafe void f0_if(byte* pbufD, short* pbufS, int cnt)
    {
    short* pS = pbufS;
    byte* pD = pbufD;
    int i;
    for (i = 0; i < cnt; ++i)
    {
    // 分别对4个通道做饱和处理
    pD[0] = (pS[0] < 0) ? (byte)0 : ((pS[0] > 255) ? (byte)255 : (byte)pS[0]);
    pD[1] = (pS[1] < 0) ? (byte)0 : ((pS[1] > 255) ? (byte)255 : (byte)pS[1]);
    pD[2] = (pS[2] < 0) ? (byte)0 : ((pS[2] > 255) ? (byte)255 : (byte)pS[2]);
    pD[3] = (pS[3] < 0) ? (byte)0 : ((pS[3] > 255) ? (byte)255 : (byte)pS[3]);
    // next
    pS += 4;
    pD += 4;
    }
    }

    // 用min、max饱和处理
    static unsafe void f1_min(byte* pbufD, short* pbufS, int cnt)
    {
    short* pS = pbufS;
    byte* pD = pbufD;
    int i;
    for (i = 0; i < cnt; ++i)
    {
    // 分别对4个通道做饱和处理
    pD[0] = (byte)Math.Min(Math.Max((byte)0, pS[0]), (byte)255);
    pD[1] = (byte)Math.Min(Math.Max((byte)0, pS[1]), (byte)255);
    pD[2] = (byte)Math.Min(Math.Max((byte)0, pS[2]), (byte)255);
    pD[3] = (byte)Math.Min(Math.Max((byte)0, pS[3]), (byte)255);
    // next
    pS += 4;
    pD += 4;
    }
    }

    // 用位掩码做饱和处理.用带符号右移生成掩码
    static unsafe void f2_neg(byte* pbufD, short* pbufS, int cnt)
    {
    unchecked
    {
    short* pS = pbufS;
    byte* pD = pbufD;
    int i;
    for (i = 0; i < cnt; ++i)
    {
    // 分别对4个通道做饱和处理
    pD[0] = (byte)(pS[0] & -Convert.ToInt16(pS[0] >= 0) | -Convert.ToInt16(pS[0] >= 256));
    pD[1] = (byte)(pS[1] & -Convert.ToInt16(pS[1] >= 0) | -Convert.ToInt16(pS[1] >= 256));
    pD[2] = (byte)(pS[2] & -Convert.ToInt16(pS[2] >= 0) | -Convert.ToInt16(pS[2] >= 256));
    pD[3] = (byte)(pS[3] & -Convert.ToInt16(pS[3] >= 0) | -Convert.ToInt16(pS[3] >= 256));
    // next
    pS += 4;
    pD += 4;
    }
    }
    }

    static byte LIMITSU_BYTE(short n)
    {
    unchecked
    {
    return (byte)(n & -Convert.ToInt16(n >= 0) | -Convert.ToInt16(n >= 256));
    }
    }

    // 调用LIMITSU_BYTE函数
    static unsafe void f2_negB(byte* pbufD, short* pbufS, int cnt)
    {
    unchecked
    {
    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;
    }
    }
    }

    // 用位掩码做饱和处理.用求负生成掩码
    static unsafe void f3_sar(byte* pbufD, short* pbufS, int cnt)
    {
    unchecked
    {
    short* pS = pbufS;
    byte* pD = pbufD;
    int i;
    for (i = 0; i < cnt; ++i)
    {
    // 分别对4个通道做饱和处理
    pD[0] = (byte)(((ushort)pS[0] | ((short)(255 - pS[0]) >> 15)) & ~(pS[0] >> 15));
    pD[1] = (byte)(((ushort)pS[1] | ((short)(255 - pS[1]) >> 15)) & ~(pS[1] >> 15));
    pD[2] = (byte)(((ushort)pS[2] | ((short)(255 - pS[2]) >> 15)) & ~(pS[2] >> 15));
    pD[3] = (byte)(((ushort)pS[3] | ((short)(255 - pS[3]) >> 15)) & ~(pS[3] >> 15));
    // next
    pS += 4;
    pD += 4;
    }
    }
    }

    static byte LIMITSW_BYTE(short n)
    {
    unchecked
    {
    return (byte)(((ushort)n | ((short)(255 - n) >> 15)) & ~(n >> 15));
    }
    }

    // 调用LIMITSW_BYTE函数
    static unsafe void f3_sarB(byte* pbufD, short* pbufS, int cnt)
    {
    unchecked
    {
    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;
    }
    }
    }

    // 进行测试
    static unsafe void runTest(string szname, TESTPROC proc)
    {
    int i, j;
    Stopwatch stw = new Stopwatch(); // 执行时间
    // 绑定指针
    fixed (short* pbufS = &bufS[0])
    {
    fixed (byte* pbufD = &bufD[0])
    {
    // 开始测试
    for (i = 1; i <= 3; ++i) // 多次测试
    {
    stw.Reset();
    stw.Start();
    // main
    for (j = 1; j <= 4000; ++j) // 重复运算几次延长时间,避免计时精度问题
    {
    proc(pbufD, pbufS, DATASIZE);
    }
    // show
    stw.Stop();
    //_tprintf(_T("%s[%d]:\t%u\n"), szname, i, tm1);
    Console.WriteLine("{0}[{1}]:\t{2}", szname, i, stw.ElapsedMilliseconds);

    }
    }
    }

    }

    static void Main(string[] args)
    {
    int i; // 循环变量

    //Console.WriteLine("Hello the world!");
    int nBitSys = (Environment.Is64BitOperatingSystem) ? 64 : 32;
    //int nBitCode = (Environment.Is64BitProcess) ? 64 : 32;
    //Console.Write("== noif:C#2010({0}) on {1}bit ==", nBitCode, nBitSys);
    #if (ISX86)
    string sBitCode = "x86";
    #elif (ISX64)
    string sBitCode = "x64";
    #else
    string sBitCode = "any";
    #endif
    Console.Write("== noif:C#2010({0}) on {1}bit ==", sBitCode, nBitSys);

    // 初始化
    Random rnd = new Random();
    for (i = 0; i < DATASIZE*4; ++i)
    {
    bufS[i] = (short)((rnd.Next() & 0x1FF) - 128); // 使数值在 [-128, 383] 区间
    }

    // 准备开始。可以将将进程优先级设为实时
    if (args.Length <= 0)
    {
    Console.Write("<Press any key to continue>");
    Console.ReadKey(true);
    }
    Console.WriteLine();

    // 进行测试
    unsafe
    {
    runTest("f0_if", f0_if);
    runTest("f1_min", f1_min);
    runTest("f2_neg", f2_neg);
    runTest("f2_negB", f2_negB);
    runTest("f3_sar", f3_sar);
    runTest("f3_sarB", f3_sarB);
    }

    // 结束前提示
    if (args.Length <= 0)
    {
    Console.Write("<Press any key to exit>");
    Console.ReadKey(true);
    }
    Console.WriteLine();

    }
    }
    }


    三、编译与测试

      点击菜单栏的 生成->批生成。生成Release版。

      生成完毕后,将会有以下三个可执行文件——
    bin\Release\noifCS2010.exe
    bin\x86\Release\noifCS2010.exe
    bin\x64\Release\noifCS2010.exe

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

    == noif:C#2010(any) on 32bit ==<Press any key to continue>
    f0_if[1]: 1935
    f0_if[2]: 1922
    f0_if[3]: 1921
    f1_min[1]: 2139
    f1_min[2]: 2144
    f1_min[3]: 2147
    f2_neg[1]: 2472
    f2_neg[2]: 2471
    f2_neg[3]: 2458
    f2_negB[1]: 2641
    f2_negB[2]: 2660
    f2_negB[3]: 2640
    f3_sar[1]: 562
    f3_sar[2]: 559
    f3_sar[3]: 557
    f3_sarB[1]: 813
    f3_sarB[2]: 823
    f3_sarB[3]: 822




    == noif:C#2010(x86) on 32bit ==<Press any key to continue>
    f0_if[1]: 1930
    f0_if[2]: 1931
    f0_if[3]: 1904
    f1_min[1]: 2153
    f1_min[2]: 2142
    f1_min[3]: 2142
    f2_neg[1]: 2437
    f2_neg[2]: 2464
    f2_neg[3]: 2462
    f2_negB[1]: 2641
    f2_negB[2]: 2637
    f2_negB[3]: 2642
    f3_sar[1]: 556
    f3_sar[2]: 545
    f3_sar[3]: 545
    f3_sarB[1]: 822
    f3_sarB[2]: 812
    f3_sarB[3]: 823

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

    == noif:C#2010(any) on 64bit ==<Press any key to continue>
    f0_if[1]: 1902
    f0_if[2]: 1871
    f0_if[3]: 1871
    f1_min[1]: 1919
    f1_min[2]: 1918
    f1_min[3]: 1916
    f2_neg[1]: 2409
    f2_neg[2]: 2406
    f2_neg[3]: 2405
    f2_negB[1]: 2956
    f2_negB[2]: 2954
    f2_negB[3]: 2954
    f3_sar[1]: 652
    f3_sar[2]: 651
    f3_sar[3]: 650
    f3_sarB[1]: 1032
    f3_sarB[2]: 1031
    f3_sarB[3]: 1031



    == noif:C#2010(x86) on 64bit ==<Press any key to continue>
    f0_if[1]: 1857
    f0_if[2]: 1829
    f0_if[3]: 1829
    f1_min[1]: 2073
    f1_min[2]: 2071
    f1_min[3]: 2076
    f2_neg[1]: 2340
    f2_neg[2]: 2342
    f2_neg[3]: 2340
    f2_negB[1]: 2540
    f2_negB[2]: 2541
    f2_negB[3]: 2540
    f3_sar[1]: 516
    f3_sar[2]: 516
    f3_sar[3]: 515
    f3_sarB[1]: 766
    f3_sarB[2]: 765
    f3_sarB[3]: 766



    == noif:C#2010(x64) on 64bit ==<Press any key to continue>
    f0_if[1]: 1883
    f0_if[2]: 1865
    f0_if[3]: 1864
    f1_min[1]: 1913
    f1_min[2]: 1911
    f1_min[3]: 1911
    f2_neg[1]: 2399
    f2_neg[2]: 2396
    f2_neg[3]: 2400
    f2_negB[1]: 2949
    f2_negB[2]: 2949
    f2_negB[3]: 2949
    f3_sar[1]: 652
    f3_sar[2]: 651
    f3_sar[3]: 651
    f3_sarB[1]: 1031
    f3_sarB[2]: 1030
    f3_sarB[3]: 1030

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


    源码下载——
    https://files.cnblogs.com/zyl910/noifCS2010.rar

    (建议阅读编译器生成的汇编代码。提示:使用ILSPY)

  • 相关阅读:
    2017年期末获奖名单
    2018-01-17作业
    3.2.4 条件表达式
    3.2.3if语句的嵌套2
    if嵌套语句--作业题
    软工第四次作业
    软工第五次作业-结对
    软工第三次作业
    软工第二次作业——数独
    软工实践2017年第一次作业
  • 原文地址:https://www.cnblogs.com/zyl910/p/noifopex5.html
Copyright © 2011-2022 走看看