银河

SKYIV STUDIO

  博客园 :: 首页 :: 博问 :: 闪存 :: :: :: 订阅 订阅 :: 管理 ::
  268 随笔 :: 2 文章 :: 2605 评论 :: 48 引用

公告

偶然在 MSDN 上看到 Math.BigMul 方法:

Math.BigMul 方法

生成两个 32 位数字的完整乘积。

命名空间:System
程序集: mscorlib(在 mscorlib.dll 中)
语法:
public static long BigMul(int a, int b)

参数:
a  类型:System.Int32,第一个乘数。
b  类型:System.Int32,第二个乘数。

返回值:
类型:System.Int64
包含指定数字乘积的 Int64。

我就想,为什么 .NET Base Class Library 要提供这么一个方法?她的功能不就是等价于 (long)a * b 吗?

那么,首先想到的就是用 .NET Reflector 这个 .NET 程序员必备的工具看看 Math.BigMul 的源程序代码:

1 public static long BigMul(int a, int b)
2 {
3     return (a * b);
4 }

咦,上面的代码好像有点不对。第 3 行应该是

return (long)a * b;

才对呀。于是,再看 IL 代码:

 1 .method public hidebysig static int64 BigMul(int32 a, int32 b) cil managed
 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 的效率:

 1 using System;
 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 方法慢呢?还是让我们来看看这个程序的实际运行情况吧:

Math.BigMul(): 00:00:06.7050475
long multiply: 00:00:06.6712870
True
Math.BigMul(): 00:00:06.6946425
long multiply: 00:00:06.6873783
True
Math.BigMul(): 00:00:06.6640827
long multiply: 00:00:06.6910634
True
Math.BigMul(): 00:00:06.6726273
long multiply: 00:00:06.6743329
True
Math.BigMul(): 00:00:06.6601853
long multiply: 00:00:06.6685163
True

可以看出,这两种方法的运行时间是一样的。

这样看来,在 C# 语言中,方法调用的时间几乎可以忽略不计。因为在 BigMultiply 方法中比 LongMultiply 方法中多了二十多亿次(准确地说,是 2,147,483,647次) Math.BigMul 方法调用。但她们的运行时间差不多。

在 MSDN 论坛上有一篇关于 Math.BigMul 的有趣的帖子

Andrew Shapira [loc] - Posted 于 2008年3月26日 3:47:24

What purpose does Math.BigMul serve, and why was Math.BigMul included in the BCL when a*(long)b achieves the same thing as Math.BigMul(a,b), for Int32 a and b?

(And why does the name 'BigMul' violate the guideline of not abbreviating names, e.g., wouldn't it have been better to call the method 'BigMultiply'?)


Philippe Leybaert [loc] - Posted 于 2008年3月26日 4:50:04 Math.BigMul() is just a convenience method, so you wouldn't need to cast the values you're multiplying (it also makes sure you don't forget to cast, because if you do, the results will be not what you expect)

Regarding your second question, I'll leave that to the .NET Framework designers

BinaryCoder [loc] - Posted 于 2008年3月26日 6:36:55

I'm sure there is a really good story about why this method was put in .NET!

Notice that in the documentation it says:

Supported in: 3.0, 2.0, 1.1

This means that BigMul was not originally in 1.0 but was added later... Interesting.

这个帖子中说到 Math.BigMul 方法是为了防止程序员在做乘法时忘记了把 int 类型转换为 long 类型。我想,作为一个熟练的程序员,应该不会犯这个错误。相反,不知道有 Math.BigMul 方法的程序员可能会更多一些。


posted on 2008-07-10 21:29 银河 阅读(...) 评论(...) 编辑 收藏