银河

SKYIV STUDIO

  博客园 :: 首页 ::  ::  :: 订阅 订阅 :: 管理 ::
  105 随笔 :: 2 文章 :: 751 评论 :: 22 Trackbacks

偶然在 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 银河 阅读(1474) 评论(12)  编辑 收藏 所属分类: .NET Framework

评论

#1楼  2008-07-10 22:12 Rect [未注册用户]
你所看到的a*b这样的方法只是C#以及多数语言有的方法,并不是所有的语言都支持这种用法的。。。。明白了么?
  回复  引用    

#2楼 [楼主] 2008-07-10 22:21 银河      
@Rect (1楼)
不明白。
什么语言不支持乘法:a * b ?
请你举例说明什么语言不支持乘法 ?
谢谢!

  回复  引用  查看    

#3楼  2008-07-10 23:34 560889223      
虽然我列举不出来这样的语言,但参考Rect的说法,可能是为了跨语言的需要吧。毕竟FCL是服务于.NET的,而非C#。
  回复  引用  查看    

#4楼  2008-07-11 07:12 Angel Lucifer      
/*
* 这样看来,在 C# 语言中,方法调用的时间几乎可以忽略不计。
*/

像这种方法体很简短的 Method ,编译器会 inline 的。如果不内联,函数调用在一定的数量级上,开销还是很大嘀。

若不是楼主提及,还真不知道 Math.BigMul 方法的存在,呵呵。

俺倾向于这种结论:Math.BigMul 方法是为了防止程序员在做乘法时忘记了把 int 类型转换为 long 类型。

但这个实在是没有多大的讨论意义,:-(
  回复  引用  查看    

#5楼  2008-07-11 08:47 玉开      
没用过这个方法。
  回复  引用  查看    

#6楼 [楼主] 2008-07-11 10:19 银河      
@Angel Lucifer (4楼)
> 像这种方法体很简短的 Method ,编译器会 inline 的。

至少 C# 编译在将 TestBigMul.cs 编译为 TestBigMul.exe 时没有内联 Math.BigMul 方法,如下所示:

IL_0016: call int64 [mscorlib] System.Math::BigMul(int32, int32)

你是不是指 JIT 编译器在运行 TestBigMul.exe 时会内联 Math.BigMul 方法?
又如何去验证 JIT 编译器是否有内联该方法呢?
  回复  引用  查看    

#7楼 [楼主] 2008-07-11 10:34 银河      
@560889223 (3楼)
> 虽然我列举不出来这样的语言,但参考Rect的说法,可能是为了跨语言的需要吧。毕竟FCL是服务于.NET的,而非C#。

不错,FCL 是服务于 .NET 的,.NET Framework 支持的语言也不仅仅是 C#。但是好象没有 .NET Framework 支持的语言不支持乘法运算和类型转换操作的。

我认为 FCL 提供 Math.BigMul 方法是为了防止程序员在做乘法时忘记了把 int 类型转换为 long 类型。

但是,如果仅仅是为了这个目的,实际上意义不大。因为很少有知道 Math.BigMul 方法的程序员会在做乘法时忘记了把 int 类型转换为 long 类型。
  回复  引用  查看    

#8楼  2008-07-11 10:42 Psic [未注册用户]
@银河

调试时候反汇编查看本机代码

  回复  引用    

#9楼 [楼主] 2008-07-11 11:14 银河      
@Psic (8楼)
> 调试时候反汇编查看本机代码

谢谢你的建议。我在 Visual Stdio 2008 IDE 中设置好断点,启动调试,在反汇编窗口得到的内容如下:

for (int i = int.MaxValue; i > 0; i--) k += Math.BigMul(i, n - i);
0000004b mov ebx,7FFFFFFFh
00000050 nop
00000051 jmp 00000090
00000053 mov eax,dword ptr [esp+4]
00000057 mov edx,dword ptr [esp+8]
0000005b mov dword ptr [esp+0Ch],eax
0000005f mov dword ptr [esp+10h],edx
00000063 mov edx,dword ptr [esp]
00000066 sub edx,ebx
00000068 mov ecx,ebx
0000006a call 75AD5278
0000006f mov dword ptr [esp+14h],eax
00000073 mov dword ptr [esp+18h],edx
00000077 mov eax,dword ptr [esp+0Ch]
0000007b mov edx,dword ptr [esp+10h]
0000007f add eax,dword ptr [esp+14h]
00000083 adc edx,dword ptr [esp+18h]
00000087 mov dword ptr [esp+4],eax
0000008b mov dword ptr [esp+8],edx
0000008f dec ebx
00000090 test ebx,ebx
00000092 jg 00000053

看来 JIT 编译器没有内联 Math.BigMul 方法。

但是我怀疑在调试时 JIT 编译器的行为和在生产环境运行时是不一样的,有可能在调试时 JIT  编译器不内联 Math.BigMul 方法,而在生产环境运行时 JIT 编译器可能会内联 Math.BigMul 方法。


  回复  引用  查看    

#10楼  2008-07-11 11:39 Psic [未注册用户]
IDE可以在配置管理器里设置当前的配置,是Debug还是Release

调试 Debug
c = Math.BigMul(a, b)
00000069 8B D3 mov edx,ebx
0000006b 8B 4D B8 mov ecx,dword ptr [ebp-48h]
0000006e E8 35 40 63 78 call 786340A8
00000073 89 45 A0 mov dword ptr [ebp-60h],eax
00000076 89 55 A4 mov dword ptr [ebp-5Ch],edx
00000079 8B 45 A0 mov eax,dword ptr [ebp-60h]
0000007c 8B 55 A4 mov edx,dword ptr [ebp-5Ch]
0000007f 89 45 A8 mov dword ptr [ebp-58h],eax
00000082 89 55 AC mov dword ptr [ebp-54h],edx
mc = c
00000085 8B 45 A8 mov eax,dword ptr [ebp-58h]
00000088 8B 55 AC mov edx,dword ptr [ebp-54h]
0000008b 89 86 4C 01 00 00 mov dword ptr [esi+0000014Ch],eax
00000091 89 96 50 01 00 00 mov dword ptr [esi+00000150h],edx


调试 Release
c = Math.BigMul(a, b)
0000001b 8B C3 mov eax,ebx
0000001d F7 EA imul edx
mc = c
0000001f 89 81 4C 01 00 00 mov dword ptr [ecx+0000014Ch],eax
00000025 89 91 50 01 00 00 mov dword ptr [ecx+00000150h],edx

  回复  引用    

#11楼 [楼主] 2008-07-11 11:59 银河      
@ Psic (10楼)
我的当前配置就是 Release,如下图所示:


不知为什么在我的机器上没有内联 Math.BigMul 方法,而在你的机器上就内联了呢?


  回复  引用  查看    

#12楼  2008-07-11 14:26 airwolf2026      
有意思...
  回复  引用  查看    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  博客园首页

  新闻频道

  社区

  小组

  博问

  网摘

  闪存

  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-07-10 22:26 编辑过
成果网帮您增加网站收入


相关链接: