银河

SKYIV STUDIO

  博客园 :: 首页 :: 博问 :: 闪存 :: :: :: 订阅 订阅 :: 管理 ::

我在 2010-12-21 写了一篇随笔“浅谈 ZipInteger”,其中讲述的 ZipInteger 结构中有以下构造函数:

01:  /// <summary>
02:  /// 使用字节数组中的值初始 ZipInteger 结构的新实例
03:  /// 注意:本构造函数会破坏传入的 bits 参数的值。
04:  /// </summary>
05:  /// <param name="bits">顺序为 big-endian 的字节值的数组</param>
06:  public ZipInteger(byte[] bits)
07:  {
08:    if (bits == null) throw new ArgumentNullException("bits");
09:    if (bits.Length < 1 || bits.Length > 9) throw new ArgumentException("Invalid length", "bits");
10:    byte[] mask = { 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00 };
11:    if (bits.Length > 1 && bits.Length < 9) bits[0] &= mask[bits.Length - 1];
12:    Array.Reverse(bits);
13:    Array.Resize(ref bits, 8);
14:    if (!BitConverter.IsLittleEndian) Array.Reverse(bits);
15:    data = Decode(BitConverter.ToInt64(bits, 0));
16:  }

注意上述源程序第 10 行的 mask 数组其实只需要初始化一次就行了,而不需要每次调用该构造函数时都进行初始化。也就是说,应该将 mask 变量声明为 static 的,如下所示:

static readonly byte[] mask = { 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00 };

愿望是美好的,现实是残酷的,加上 static 关键字后,再编译时 C# 编译器会报告以下错误:

error CS0106: 修饰符“static”对该项无效

也就是说,C# 语言不允许使用 static 修饰符来声明方法内部的变量。但是在 C/C++ 语言中是允许这么做的。

如果把该构造函数内部的 mask 变量提升到构造函数外部,成为 ZipInteger 结构的字段成员,就可以声明为 static 的。但是这样一样,读这段代码的人就不容易弄清楚 mask 字段只在这个构造函数内部使用,成为代码的“坏味道”,一点也不优雅了。

 

好了,让我们写一小段程序来测试一下加上 static 后对运行效率的影响:

01:  using System;
02:  using System.IO;
03:  using System.Diagnostics;
04:  using Skyiv.Numerics;
05:  
06:  namespace Skyiv.Tester
07:  {
08:    sealed class StaticTester
09:    {
10:      static void Main()
11:      {
12:        try
13:        {
14:          new StaticTester().Run(100000000);
15:        }
16:        catch (Exception ex)
17:        {
18:          Console.WriteLine(ex);
19:        }
20:      }
21:  
22:      void Run(int count)
23:      {
24:        Console.WriteLine("  OS Version: " + Environment.OSVersion);
25:        Console.WriteLine(" CLR Version: " + Environment.Version);
26:        using (var reader = new MemoryStream(GetBytes(count)))
27:        {
28:          var watch = Stopwatch.StartNew();
29:          var n = 0;
30:          for (var i = 0; i < 10; i++)
31:          {
32:            reader.Seek(0, SeekOrigin.Begin);
33:            while (ZipInteger.Read(reader).HasValue) n++;
34:          }
35:          watch.Stop();
36:          Console.WriteLine("       Count: " + n.ToString("N0"));
37:          Console.WriteLine("Milliseconds: " + watch.ElapsedMilliseconds.ToString("N0"));
38:        }
39:      }
40:  
41:      byte[] GetBytes(int count)
42:      {
43:        var bits = new byte[count];
44:        var rand = new Random(123456);
45:        rand.NextBytes(bits);
46:        return bits;
47:      }
48:    }
49:  }

上述程序中第 44 行使用固定的种子初始化 Random 类的新实例,从而产生相同的随机数序列,以便用相同的测试用例来进行测试,使测试结果具有可比性。注意,在上述程序中如果 count 值和随机数的种子选取得不好,执行到第 33 行的 ZipInteger.Read 方法时是有可能抛出 EndOfStreamException 异常的。

 

这个测试程序在 Windows Vista 操作系统的 .NET Framework 4 环境下的运行结果如下所示:

  OS Version: Microsoft Windows NT 6.0.6002 Service Pack 2
 CLR Version: 4.0.30319.1
       Count: 500,990,730
Milliseconds: 181,886

将这个测试程序对 mask 变量是非静态的和静态的两种情况分别运行五次,得到的 Milliseconds 值如下表所示:

序号 非静态 静态
1 181,886 167,528
2 179,074 166,847
3 180,309 166,526
4 183,542 166,399
5 179,469 167,365
平均 180,856 166,933

经过简单的计算得知,静态情况下比非静态情况下的平均运行效率提高了 7.7%,还是很可观的。

 

这个测试程序在 Ubuntu 10.10 操作系统的 mono 2.6.7 环境的运行结果如下所示:

非静态 静态
  OS Version: Unix 2.6.35.24
 CLR Version: 2.0.50727.1433
       Count: 500,992,030
Milliseconds: 660,254
  OS Version: Unix 2.6.35.24
 CLR Version: 2.0.50727.1433
       Count: 500,992,030
Milliseconds: 583,387

经过简单的计算得知,静态情况下比非静态情况下的运行效率提高了 11.6%,更是有了较大的改进。

 

我们来看看这两种情况下 C# 编译器生成的 IL 代码,在非静态的情况下的该实例构造函数(.ctor)的 IL 代码:

001:  .method public hidebysig specialname rtspecialname 
002:          instance void  .ctor(uint8[] bits) cil managed
003:  {
004:    // 代码大小       181 (0xb5)
005:    .maxstack  5
006:    .locals init (uint8[] V_0,
007:             bool V_1)
008:    IL_0000:  nop
009:    IL_0001:  ldarg.1
010:    IL_0002:  ldnull
011:    IL_0003:  ceq
012:    IL_0005:  ldc.i4.0
013:    IL_0006:  ceq
014:    IL_0008:  stloc.1
015:    IL_0009:  ldloc.1
016:    IL_000a:  brtrue.s   IL_0017
017:    IL_000c:  ldstr      "bits"
018:    IL_0011:  newobj     instance void [mscorlib]System.ArgumentNullException::.ctor(string)
019:    IL_0016:  throw
020:    IL_0017:  ldarg.1
021:    IL_0018:  ldlen
022:    IL_0019:  conv.i4
023:    IL_001a:  ldc.i4.1
024:    IL_001b:  blt.s      IL_0029
025:    IL_001d:  ldarg.1
026:    IL_001e:  ldlen
027:    IL_001f:  conv.i4
028:    IL_0020:  ldc.i4.s   9
029:    IL_0022:  cgt
030:    IL_0024:  ldc.i4.0
031:    IL_0025:  ceq
032:    IL_0027:  br.s       IL_002a
033:    IL_0029:  ldc.i4.0
034:    IL_002a:  stloc.1
035:    IL_002b:  ldloc.1
036:    IL_002c:  brtrue.s   IL_003e
037:    IL_002e:  ldstr      "Invalid length"
038:    IL_0033:  ldstr      "bits"
039:    IL_0038:  newobj     instance void [mscorlib]System.ArgumentException::.ctor(string,
040:                                                                                 string)
041:    IL_003d:  throw
042:    IL_003e:  ldc.i4.8
043:    IL_003f:  newarr     [mscorlib]System.Byte
044:    IL_0044:  dup
045:    IL_0045:  ldtoken    field int64 '<PrivateImplementationDetails>
046:                           {78063CDC-E5EE-4C6B-A62D-FD0F919F6111}'::'$$method0x6000006-1'
047:    IL_004a:  call       void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::
048:                           InitializeArray(class [mscorlib]System.Array,
049:                             valuetype [mscorlib]System.RuntimeFieldHandle)
050:    IL_004f:  stloc.0
051:    IL_0050:  ldarg.1
052:    IL_0051:  ldlen
053:    IL_0052:  conv.i4
054:    IL_0053:  ldc.i4.1
055:    IL_0054:  ble.s      IL_0062
056:    IL_0056:  ldarg.1
057:    IL_0057:  ldlen
058:    IL_0058:  conv.i4
059:    IL_0059:  ldc.i4.s   9
060:    IL_005b:  clt
061:    IL_005d:  ldc.i4.0
062:    IL_005e:  ceq
063:    IL_0060:  br.s       IL_0063
064:    IL_0062:  ldc.i4.1
065:    IL_0063:  stloc.1
066:    IL_0064:  ldloc.1
067:    IL_0065:  brtrue.s   IL_0082
068:    IL_0067:  ldarg.1
069:    IL_0068:  ldc.i4.0
070:    IL_0069:  ldelema    [mscorlib]System.Byte
071:    IL_006e:  dup
072:    IL_006f:  ldobj      [mscorlib]System.Byte
073:    IL_0074:  ldloc.0
074:    IL_0075:  ldarg.1
075:    IL_0076:  ldlen
076:    IL_0077:  conv.i4
077:    IL_0078:  ldc.i4.1
078:    IL_0079:  sub
079:    IL_007a:  ldelem.u1
080:    IL_007b:  and
081:    IL_007c:  conv.u1
082:    IL_007d:  stobj      [mscorlib]System.Byte
083:    IL_0082:  ldarg.1
084:    IL_0083:  call       void [mscorlib]System.Array::Reverse(class [mscorlib]System.Array)
085:    IL_0088:  nop
086:    IL_0089:  ldarga.s   bits
087:    IL_008b:  ldc.i4.8
088:    IL_008c:  call       void [mscorlib]System.Array::Resize<uint8>(!!0[]&,
089:                                                                    int32)
090:    IL_0091:  nop
091:    IL_0092:  ldsfld     bool [mscorlib]System.BitConverter::IsLittleEndian
092:    IL_0097:  stloc.1
093:    IL_0098:  ldloc.1
094:    IL_0099:  brtrue.s   IL_00a2
095:    IL_009b:  ldarg.1
096:    IL_009c:  call       void [mscorlib]System.Array::Reverse(class [mscorlib]System.Array)
097:    IL_00a1:  nop
098:    IL_00a2:  ldarg.0
099:    IL_00a3:  ldarg.1
100:    IL_00a4:  ldc.i4.0
101:    IL_00a5:  call       int64 [mscorlib]System.BitConverter::ToInt64(uint8[],
102:                                                                      int32)
103:    IL_00aa:  call       int64 Skyiv.Numerics.ZipInteger::Decode(int64)
104:    IL_00af:  stfld      int64 Skyiv.Numerics.ZipInteger::data
105:    IL_00b4:  ret
106:  } // end of method ZipInteger::.ctor

上述 IL 代码的第 42 行到第 49 行对 mask 数组进行初始化,紧接着在第 50 行用“stloc.0”指令保存起来,然后在第 73 行用“ldloc.0”指令引用。

 

再看看静态的情况下该实例构造函数(.ctor)的 IL 代码:

01:  .method public hidebysig specialname rtspecialname 
02:          instance void  .ctor(uint8[] bits) cil managed
03:  {
04:    // 代码大小       167 (0xa7)
05:    .maxstack  5
06:    .locals init (bool V_0)
07:    IL_0000:  nop
08:    IL_0001:  ldarg.1
09:    IL_0002:  ldnull
10:    IL_0003:  ceq
11:    IL_0005:  ldc.i4.0
12:    IL_0006:  ceq
13:    IL_0008:  stloc.0
14:    IL_0009:  ldloc.0
15:    IL_000a:  brtrue.s   IL_0017
16:    IL_000c:  ldstr      "bits"
17:    IL_0011:  newobj     instance void [mscorlib]System.ArgumentNullException::.ctor(string)
18:    IL_0016:  throw
19:    IL_0017:  ldarg.1
20:    IL_0018:  ldlen
21:    IL_0019:  conv.i4
22:    IL_001a:  ldc.i4.1
23:    IL_001b:  blt.s      IL_0029
24:    IL_001d:  ldarg.1
25:    IL_001e:  ldlen
26:    IL_001f:  conv.i4
27:    IL_0020:  ldc.i4.s   9
28:    IL_0022:  cgt
29:    IL_0024:  ldc.i4.0
30:    IL_0025:  ceq
31:    IL_0027:  br.s       IL_002a
32:    IL_0029:  ldc.i4.0
33:    IL_002a:  stloc.0
34:    IL_002b:  ldloc.0
35:    IL_002c:  brtrue.s   IL_003e
36:    IL_002e:  ldstr      "Invalid length"
37:    IL_0033:  ldstr      "bits"
38:    IL_0038:  newobj     instance void [mscorlib]System.ArgumentException::.ctor(string,
39:                                                                                 string)
40:    IL_003d:  throw
41:    IL_003e:  ldarg.1
42:    IL_003f:  ldlen
43:    IL_0040:  conv.i4
44:    IL_0041:  ldc.i4.1
45:    IL_0042:  ble.s      IL_0050
46:    IL_0044:  ldarg.1
47:    IL_0045:  ldlen
48:    IL_0046:  conv.i4
49:    IL_0047:  ldc.i4.s   9
50:    IL_0049:  clt
51:    IL_004b:  ldc.i4.0
52:    IL_004c:  ceq
53:    IL_004e:  br.s       IL_0051
54:    IL_0050:  ldc.i4.1
55:    IL_0051:  stloc.0
56:    IL_0052:  ldloc.0
57:    IL_0053:  brtrue.s   IL_0074
58:    IL_0055:  ldarg.1
59:    IL_0056:  ldc.i4.0
60:    IL_0057:  ldelema    [mscorlib]System.Byte
61:    IL_005c:  dup
62:    IL_005d:  ldobj      [mscorlib]System.Byte
63:    IL_0062:  ldsfld     uint8[] Skyiv.Numerics.ZipInteger::mask
64:    IL_0067:  ldarg.1
65:    IL_0068:  ldlen
66:    IL_0069:  conv.i4
67:    IL_006a:  ldc.i4.1
68:    IL_006b:  sub
69:    IL_006c:  ldelem.u1
70:    IL_006d:  and
71:    IL_006e:  conv.u1
72:    IL_006f:  stobj      [mscorlib]System.Byte
73:    IL_0074:  ldarg.1
74:    IL_0075:  call       void [mscorlib]System.Array::Reverse(class [mscorlib]System.Array)
75:    IL_007a:  nop
76:    IL_007b:  ldarga.s   bits
77:    IL_007d:  ldc.i4.8
78:    IL_007e:  call       void [mscorlib]System.Array::Resize<uint8>(!!0[]&,
79:                                                                    int32)
80:    IL_0083:  nop
81:    IL_0084:  ldsfld     bool [mscorlib]System.BitConverter::IsLittleEndian
82:    IL_0089:  stloc.0
83:    IL_008a:  ldloc.0
84:    IL_008b:  brtrue.s   IL_0094
85:    IL_008d:  ldarg.1
86:    IL_008e:  call       void [mscorlib]System.Array::Reverse(class [mscorlib]System.Array)
87:    IL_0093:  nop
88:    IL_0094:  ldarg.0
89:    IL_0095:  ldarg.1
90:    IL_0096:  ldc.i4.0
91:    IL_0097:  call       int64 [mscorlib]System.BitConverter::ToInt64(uint8[],
92:                                                                      int32)
93:    IL_009c:  call       int64 Skyiv.Numerics.ZipInteger::Decode(int64)
94:    IL_00a1:  stfld      int64 Skyiv.Numerics.ZipInteger::data
95:    IL_00a6:  ret
96:  } // end of method ZipInteger::.ctor

上述 IL 代码中就没有对 mask 数组进行初始化,而是在第 63 行用“ldsfld”指令直接引用 ZipInteger 结构的静态字段,即已经初始化好了的 mask 数组。这个 mask 数组是在 ZipInteger 结构的静态构造函数(.cctor)中初始化的,如下所示:

01:  .method private hidebysig specialname rtspecialname static 
02:          void  .cctor() cil managed
03:  {
04:    // 代码大小       61 (0x3d)
05:    .maxstack  8
06:    IL_0000:  ldc.i8     0x8000000000000000
07:    IL_0009:  call       valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::op_Implicit(int64)
08:    IL_000e:  stsfld     valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::MinValue
09:    IL_0013:  ldc.i8     0x7fffffffffffffff
10:    IL_001c:  call       valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::op_Implicit(int64)
11:    IL_0021:  stsfld     valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::MaxValue
12:    IL_0026:  ldc.i4.8
13:    IL_0027:  newarr     [mscorlib]System.Byte
14:    IL_002c:  dup
15:    IL_002d:  ldtoken    field int64 '<PrivateImplementationDetails>
16:                           {CDCDEB38-994E-4730-8D14-55B1DBDE4B1B}'::'$$method0x6000016-1'
17:    IL_0032:  call       void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::
18:                           InitializeArray(class [mscorlib]System.Array,
19:                             valuetype [mscorlib]System.RuntimeFieldHandle)
20:    IL_0037:  stsfld     uint8[] Skyiv.Numerics.ZipInteger::mask
21:    IL_003c:  ret
22:  } // end of method ZipInteger::.cctor

可以看出,在上述 IL 代码中的第 12 到第 20 行对静态的 mask 数组进行了初始化。

 

相对应的非静态情况下的 ZipInteger 结构的静态构造函数(.cctor)就没有对 mask 数组进行初始化的语句:

01:  .method private hidebysig specialname rtspecialname static 
02:          void  .cctor() cil managed
03:  {
04:    // 代码大小       39 (0x27)
05:    .maxstack  8
06:    IL_0000:  ldc.i8     0x8000000000000000
07:    IL_0009:  call       valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::op_Implicit(int64)
08:    IL_000e:  stsfld     valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::MinValue
09:    IL_0013:  ldc.i8     0x7fffffffffffffff
10:    IL_001c:  call       valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::op_Implicit(int64)
11:    IL_0021:  stsfld     valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::MaxValue
12:    IL_0026:  ret
13:  } // end of method ZipInteger::.cctor

 

我们知道,静态构造函数总共只执行一次,而实例构造函数在每次实例化时都要执行一行。也就是说,在我们的测试用例中,mask 数组在静态的情况下只要初始化一次,而在非静态的情况下要初始化 500,990,730 次。所以运行效率出现这么大的差别就很好理解了。

 

除此之外,将方法内部的变量声明为 static 的,还可以起着方法内置状态的作用,这也是很有好处的。具体例子请参见“隐约有歌”大大的博文“技术面试题:f(f(n)) == -n”。

 

综上所述,我强烈要求在 C# 5.0 中增加支持将方法内部的变量声明为 static 的。这在 C/C++ 语言中早就已经支持了,我想这应该不难实现,因为 Microsoft 的 C/C++ 也支持这一特性。

参考资料

  1. MSDN: static (C# 参考)
  2. MSDN: static (C++)
  3. MSDN: Random 构造函数 (Int32)
posted on 2010-12-25 23:17  银河  阅读(6104)  评论(60编辑  收藏  举报