偶然在 MSDN 上看到 Math.BigMul 方法:
我就想,为什么 .NET Base Class Library 要提供这么一个方法?她的功能不就是等价于 (long)a * b 吗?
那么,首先想到的就是用 .NET Reflector 这个 .NET 程序员必备的工具看看 Math.BigMul 的源程序代码:
2 {
3 return (a * b);
4 }
咦,上面的代码好像有点不对。第 3 行应该是
return (long)a * b;
才对呀。于是,再看 IL 代码:
2 {
3 .maxstack 8
4 L_0000: ldarg.0
5 L_0001: conv.i8
6 L_0002: ldarg.1
7 L_0003: conv.i8
8 L_0004: mul
9 L_0005: ret
10 }
这下对了,第 5 行和第 7 行的 conv.i8 指令将两个 int 参数转换为 long 类型,然后再调用 mul 指令(第 8 行)进行乘法运算。
不过,这样一来,Math.BigMul 方法不就完全没有存在的必要吗?调用她还不如我们自己写 (long)a * b 语句来代替她,还省了一次方法调用的开销。我原来以为 IL 语言中有什么指令可以直接将两个 int 类型的操作数相乘得到 long 类型的结果呢,而 Math.BigMul 方法就直接调用该指令。
于是,我就写了以下的程序来比较 Math.BigMul 方法和 (long)a * b 的效率:
2 using System.Diagnostics;
3
4 namespace Skyiv.Ben
5 {
6 sealed class TestBigMul
7 {
8 static void Main()
9 {
10 for (int i = 0; i < 5; i++) Console.WriteLine(BigMultiply(i) == LongMultiply(i));
11 }
12
13 static long BigMultiply(int n)
14 {
15 long k = 0;
16 var watch = Stopwatch.StartNew();
17 for (int i = int.MaxValue; i > 0; i--) k += Math.BigMul(i, n - i);
18 watch.Stop();
19 Console.WriteLine("Math.BigMul(): " + watch.Elapsed);
20 return k;
21 }
22
23 static long LongMultiply(int n)
24 {
25 long k = 0;
26 var watch = Stopwatch.StartNew();
27 for (int i = int.MaxValue; i > 0; i--) k += (long)i * (n - i);
28 watch.Stop();
29 Console.WriteLine("long multiply: " + watch.Elapsed);
30 return k;
31 }
32 }
33 }
这个程序关键部分的 IL 代码如下:
BigMultiply | LongMultiply |
---|---|
IL_0008: stloc.1 IL_0009: ldc.i4 0x7fffffff IL_000e: stloc.2 IL_000f: br.s IL_0021 IL_0011: ldloc.0 IL_0012: ldloc.2 IL_0013: ldarg.0 IL_0014: ldloc.2 IL_0015: sub IL_0016: call int64 [mscorlib] System.Math::BigMul (int32, int32) IL_001b: add IL_001c: stloc.0 IL_001d: ldloc.2 IL_001e: ldc.i4.1 IL_001f: sub IL_0020: stloc.2 IL_0021: ldloc.2 IL_0022: ldc.i4.0 IL_0023: bgt.s IL_0011 IL_0025: ldloc.1 |
IL_0008: stloc.1 IL_0009: ldc.i4 0x7fffffff IL_000e: stloc.2 IL_000f: br.s IL_001f IL_0011: ldloc.0 IL_0012: ldloc.2 IL_0013: conv.i8 IL_0014: ldarg.0 IL_0015: ldloc.2 IL_0016: sub IL_0017: conv.i8 IL_0018: mul IL_0019: add IL_001a: stloc.0 IL_001b: ldloc.2 IL_001c: ldc.i4.1 IL_001d: sub IL_001e: stloc.2 IL_001f: ldloc.2 IL_0020: ldc.i4.0 IL_0021: bgt.s IL_0011 IL_0023: ldloc.1 |
上表中的 IL 代码和我们预计的一样。在 BigMultiply 方法中就是直接调用 Math.BigMul 方法,而在 LongMultiply 方法中先用两个 conv.i8 指令将两个 int 类型的操作数转换为 long 类型,然后再使用 mul 指令进行乘法运算。而 Math.BigMul 方法内部也是这么做的。那么,这是不是说 BigMultiply 方法就一定比 LongMultiply 方法慢呢?还是让我们来看看这个程序的实际运行情况吧:
可以看出,这两种方法的运行时间是一样的。
这样看来,在 C# 语言中,方法调用的时间几乎可以忽略不计。因为在 BigMultiply 方法中比 LongMultiply 方法中多了二十多亿次(准确地说,是 2,147,483,647次) Math.BigMul 方法调用。但她们的运行时间差不多。
在 MSDN 论坛上有一篇关于 Math.BigMul 的有趣的帖子:
这个帖子中说到 Math.BigMul 方法是为了防止程序员在做乘法时忘记了把 int 类型转换为 long 类型。我想,作为一个熟练的程序员,应该不会犯这个错误。相反,不知道有 Math.BigMul 方法的程序员可能会更多一些。