.NET Framework 2.0中增加了一项新的诊断工具——“停表”StopWatch类,相比以前的计时系统,停表采用的是高精度计时方式,因此对性能测量更有帮助。为了试用StopWatch类,我选择了一个经典的测试题目,VB中的IIf函数。IIf函数与C系列的?:三元运算符十分相似,可以在一行语句内完成判断和赋值两项任务。但是,IIf是函数而不是运算符,其接受Object类型参数这一项已经够让人担忧的了。IIf到底有多慢呢?我们比较三个版本。首先是VB类库中的Object版:
Public Function IIf(Expression As Boolean, _
TruePart As Object, FalsePart As Object)As Object
第二个版本,是我编写的泛型版本的IIf,这个版本可以用于减少装箱拆箱过程带来的性能损失:
Public Function IIf(Of T)(Expression As Boolean, _
TruePart As T, FalsePart As T)As T
If Expression Then
Return TruePart
Else
Return FalsePart
End If
End Function
第三个版本当然是直接使用If语句。我们已经很清楚哪个比较快,但是没有一个量的概念,通过高精度计时的测试,可以让我看到函数内联化带来的收益是否够多。下面是计时和测试的代码:
Dim watch As New System.Diagnostics.Stopwatch
Dim j As Integer = 0
Dim f As Boolean = True
'Loop 10 times and get the average time
For times As Integer = 1 To 10
watch.Start()
For i As Integer = 0 To 1000000
j = Interaction.IIf(f, i, 5)
Next
watch.Stop()
Next
GC.Collect()
TextBox1.AppendText("Object IIf: " & watch.ElapsedMilliseconds / 10 & vbCrLf)
watch.Reset()
For times As Integer = 1 To 10
watch.Start()
For i As Integer = 0 To 1000000
j = IIf(f, i, 5)
Next
watch.Stop()
Next
GC.Collect()
TextBox1.AppendText("Generic IIf: " & watch.ElapsedMilliseconds / 10 & vbCrLf)
watch.Reset()
For times As Integer = 1 To 10
watch.Start()
For i As Integer = 0 To 1000000
If f Then j = i Else j = 5
Next
watch.Stop()
Next
TextBox1.AppendText("Pure If: " & watch.ElapsedMilliseconds / 10 & vbCrLf)
我们看到StopWatch可以像真的停表一样开始,停止或继续,因此我们可以像在科学实验中计时一样使用它。而他的Elapsed属性可以提供各种单位的时间间隔。简单起见,我就用了毫秒。循环的次数不太多,取10次平均。最后的结果在我的机器上如下:
Object IIf: 219.6
Generic IIf: 51.5
Pure If: 9.7
纯If仅9.7毫秒的间隔也能测试出来,足见StopWatch的精度比原来的计时手段高多了。而结果也表明,函数调用的代价已经相当可观,但还不如装箱、拆箱带来的代价高。IIf当然是不要再用了,今后自己写函数时也应当注意,频繁调用的函数不能不考虑调用代价,内联化是不错的解决方案。
打印 | 张贴于 2004-07-31 13:29:00 | Tag:技术随笔
留言反馈
我装了FRAMWORK 2.0可是编译的时候却说 类未定义
如果有其他的app在跑,这三处调用,可能会对server的性能造成很大影响的吧?
#if DEBUG
MessageBox.Show("Debug");
#else
MessageBox.Show("Release");
#endif
如果是A的话,那么就应该有机会内联化。因为一切都是可推测的,固定的。实际上内联化的过程不是在编译阶段的时候进行的,而是在JIT阶段进行的,甚至应该在两到三次的JIT之后进行的。还是觉得内联化是有可能的事情。
会是A,泛型不会对编译期间任何不清楚的东西加以推测。如果你给两个参数分别传递A类型和B类型的变量,就会出错,会要求用Of显式指定类型参数。
为什么不会内联化呢?我是说这个说法有没有根据?我没有看到有说泛型函数不会内联化的说法,当然我也没有排除这个可能性。但是从我的想象来说,应该是可以内联的,因为虽然你的函数本身没有办法确定T的类型,但是调用方却可以确定使用的是哪一个类型的函数。不过这个我也不能够确定……
顺带请教你一个问题:如果有
class A{}
class B:A{}
A x = new B();
那么
IIf(x,...)这样的调用,你说实际应该是T = A 还是 T = B呢?
我用了VB Profile,那Debug/Releas的选项就不知道跑哪里去了:(,不过我是热身10遍才测试的。至于自动内联化,我觉得泛型函数是不会内联化的,所以我才用它而不是直接用Integer类型的IIf。
我在想,不知道你这里是不是用了Debug版本进行测试的?我在想,如果用Release的话,可能你的那个IIf(Of T)的函数应该会被内联化的,看起来这个函数的IL不会超过32个,也没有复杂的控制转移。另外,可能你直接这么测回把JIT时间也测出来了。如果可以的话,调用函数之前最好先热身大约十次,然后再测试,这样可能会比较准确一点。
是啊,确实方便很多。有空研究一下它内部的实现机制吧,也许用的就是PerformanceCounter呢。:)
以前当然要用那个,但是每次都要声明API,我经常想不起来那些函数的名称。现在有了StopWatch,才可以做到“想侧就测”
沒想到這麼大呀。