【IT168 技术文档】ZipInteger 结构中有以下构造函数:
/// 使用字节数组中的值初始 ZipInteger 结构的新实例
/// 注意:本构造函数会破坏传入的 bits 参数的值。
/// </summary>
/// <param name="bits">顺序为 big-endian 的字节值的数组</param>
public ZipInteger(byte[] bits)
{
if (bits == null) throw new ArgumentNullException("bits");
if (bits.Length < 1 || bits.Length > 9) throw new ArgumentException("Invalid length", "bits");
byte[] mask = { 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00 };
if (bits.Length > 1 && bits.Length < 9) bits[0] &= mask[bits.Length - 1];
Array.Reverse(bits);
Array.Resize(ref bits, 8);
if (!BitConverter.IsLittleEndian) Array.Reverse(bits);
data = Decode(BitConverter.ToInt64(bits, 0));
}
注意上述源程序第 10 行的 mask 数组其实只需要初始化一次就行了,而不需要每次调用该构造函数时都进行初始化。也就是说,应该将 mask 变量声明为 static 的,如下所示:
愿望是美好的,现实是残酷的,加上 static 关键字后,再编译时 C# 编译器会报告以下错误:
也就是说,C# 语言不允许使用 static 修饰符来声明方法内部的变量。但是在 C/C++ 语言中是允许这么做的。
如果把该构造函数内部的 mask 变量提升到构造函数外部,成为 ZipInteger 结构的字段成员,就可以声明为 static 的。但是这样一样,读这段代码的人就不容易弄清楚 mask 字段只在这个构造函数内部使用,成为代码的“坏味道”,一点也不优雅了。
好了,让我们写一小段程序来测试一下加上 static 后对运行效率的影响:
using System.IO;
using System.Diagnostics;
using Skyiv.Numerics;
namespace Skyiv.Tester
{
sealed class StaticTester
{
static void Main()
{
try
{
new StaticTester().Run(100000000);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
void Run(int count)
{
Console.WriteLine(" OS Version: " + Environment.OSVersion);
Console.WriteLine(" CLR Version: " + Environment.Version);
using (var reader = new MemoryStream(GetBytes(count)))
{
var watch = Stopwatch.StartNew();
var n = 0;
for (var i = 0; i < 10; i++)
{
reader.Seek(0, SeekOrigin.Begin);
while (ZipInteger.Read(reader).HasValue) n++;
}
watch.Stop();
Console.WriteLine(" Count: " + n.ToString("N0"));
Console.WriteLine("Milliseconds: " + watch.ElapsedMilliseconds.ToString("N0"));
}
}
byte[] GetBytes(int count)
{
var bits = new byte[count];
var rand = new Random(123456);
rand.NextBytes(bits);
return bits;
}
}
}
上述程序中第 44 行使用固定的种子初始化 Random 类的新实例,从而产生相同的随机数序列,以便用相同的测试用例来进行测试,使测试结果具有可比性。注意,在上述程序中如果 count 值和随机数的种子选取得不好,执行到第 33 行的 ZipInteger.Read 方法时是有可能抛出 EndOfStreamException 异常的。
这个测试程序在 Windows Vista 操作系统的 .NET Framework 4 环境下的运行结果如下所示:
CLR Version: 4.0.30319.1
Count: 500,990,730
Milliseconds: 181,886
将这个测试程序对 mask 变量是非静态的和静态的两种情况分别运行五次,得到的 Milliseconds 值如下表所示:
经过简单的计算得知,静态情况下比非静态情况下的平均运行效率提高了 7.7%,还是很可观的。
这个测试程序在 Ubuntu 10.10 操作系统的 mono 2.6.7 环境的运行结果如下所示:
经过简单的计算得知,静态情况下比非静态情况下的运行效率提高了 11.6%,更是有了较大的改进。
我们来看看这两种情况下 C# 编译器生成的 IL 代码,在非静态的情况下的该实例构造函数(.ctor)的 IL 代码:
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 代码:
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)中初始化的,如下所示:
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 数组进行初始化的语句:
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++ 也支持这一特性。