其中一个使用的资源多于另一个吗?

时间:2011-08-02 22:49:51

标签: .net performance

这两个代码块的背景有何不同?一个被认为比另一个更“好”吗?

我的想法是Example2可能会更糟,因为它可能不得不等待垃圾收集器处理该项目,但我不知道垃圾收集器是否知道这是否属实。

例1:

ListItem item;
for (int i = 1; i <= 32; i++)
{
   item = new ListItem();
   //do some stuff
}

例2:

for (int i = 1; i <= 32; i++)
{
   ListItem item = new ListItem();
   //do some stuff
}

5 个答案:

答案 0 :(得分:10)

我已将您的代码复制到Visual Studio中,编译它,然后查看生成的IL。这是从例1生成的IL:

.method private hidebysig static void  One() cil managed
{
  // Code size       30 (0x1e)
  .maxstack  2
  .locals init ([0] class WinTest.ListItem item,
           [1] int32 i,
           [2] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  stloc.1
  IL_0003:  br.s       IL_0011
  IL_0005:  nop
  IL_0006:  newobj     instance void WinTest.ListItem::.ctor()
  IL_000b:  stloc.0
  IL_000c:  nop
  IL_000d:  ldloc.1
  IL_000e:  ldc.i4.1
  IL_000f:  add
  IL_0010:  stloc.1
  IL_0011:  ldloc.1
  IL_0012:  ldc.i4.s   32
  IL_0014:  cgt
  IL_0016:  ldc.i4.0
  IL_0017:  ceq
  IL_0019:  stloc.2
  IL_001a:  ldloc.2
  IL_001b:  brtrue.s   IL_0005
  IL_001d:  ret
} // end of method Program::One

这是从例2生成的IL:

.method private hidebysig static void  Two() cil managed
{
  // Code size       30 (0x1e)
  .maxstack  2
  .locals init ([0] int32 i,
           [1] class WinTest.ListItem item,
           [2] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  stloc.0
  IL_0003:  br.s       IL_0011
  IL_0005:  nop
  IL_0006:  newobj     instance void WinTest.ListItem::.ctor()
  IL_000b:  stloc.1
  IL_000c:  nop
  IL_000d:  ldloc.0
  IL_000e:  ldc.i4.1
  IL_000f:  add
  IL_0010:  stloc.0
  IL_0011:  ldloc.0
  IL_0012:  ldc.i4.s   32
  IL_0014:  cgt
  IL_0016:  ldc.i4.0
  IL_0017:  ceq
  IL_0019:  stloc.2
  IL_001a:  ldloc.2
  IL_001b:  brtrue.s   IL_0005
  IL_001d:  ret
} // end of method Program::Two

据我了解,它们是相同的,除了本地人以相反的顺序声明(并因此被访问)这一事实。我不认为这会对性能产生任何影响。

答案 1 :(得分:4)

这取决于“//做一些事情”代表什么。

在一个简单的程序中,两个示例都将编译为相同的MSIL字节代码。

但是如果在循环中创建一个匿名委托,可能是在另一个线程中执行,则创建引用变量“item”,那么在循环内部或外部声明“item”是很重要的。如果,如在示例2中,“item”在循环内声明,那么当委托运行时,它将看到在创建委托的循环的迭代中分配的“item”的值(这很可能是在这些情况)。如果,如在示例1中,“item”在循环之外被声明,则委托将在其执行时看到分配的值,该值可以来自比创建委托的迭代更晚的迭代。这会导致混乱的竞争条件。

基本上,虽然C#编译的MSIL字节代码不代表变量范围,但范围在C#中是有意义的,并且可以影响其各种语法糖的行为。

答案 2 :(得分:3)

令人惊讶的是,我的测试显示示例2更快。

输出。案例1是单一声明。案例2是循环声明。

Case 1: 10.280418100
Case 2: 10.264818000 99.848254226%
Case 1: 10.592418600
Case 2: 10.140017800 95.729013202%
Case 1: 10.233618000
Case 2: 10.108817800 98.780487996%
Case 1: 10.155617800
Case 2: 10.046417600 98.924731098%
Case 1: 10.503818600
Case 2: 10.246319800 97.548522020%
Case 1: 10.243018400
Case 2: 10.030817600 97.928337217%
Case 1: 10.077617700
Case 2: 10.218017900 101.393188392%
Case 1: 10.303019300
Case 2: 10.526318800 102.167320991%
Case 1: 10.353619900
Case 2: 10.276219400 99.252430544%
Case 1: 10.264818100
Case 2: 10.202417900 99.392096388%

Case 1 Total: 103.007984500
Case 2 Total: 102.060182600 99.079875308%

代码:

Public Sub Main()
    Dim Case1Total As Double = 0
    Dim Case2Total As Double = 0
    For i As Integer = 1 To 10
        Dim Case1 As Double = MeasureTime(AddressOf Case1Method).TotalSeconds
        Case1Total += Case1
        Console.WriteLine("Case 1: {0:N9}", Case1)
        Dim Case2 As Double = MeasureTime(AddressOf Case2Method).TotalSeconds
        Case2Total += Case2
        Console.WriteLine("Case 2: {0:N9} {1:N9}%", Case2, 100 * Case2 / Case1)
    Next i
    Console.WriteLine()
    Console.WriteLine("Case 1 Total: {0:N9}", Case1Total)
    Console.WriteLine("Case 2 Total: {0:N9} {1:N9}%", Case2Total, 100 * Case2Total / Case1Total)
    Console.ReadLine()
End Sub

Private Function MeasureTime(Method As Action) As TimeSpan
    Dim StartTime As Date = Date.Now
    Method()
    Return Date.Now - StartTime
End Function

Private Sub Case1Method()
    Dim o As Label
    For i As Integer = 0 To Limit
        o = New Label
        o.Text = "Label" & i.ToString
        o.Dispose()
    Next
End Sub

Private Sub Case2Method()
    For i As Integer = 0 To Limit
        Dim o As New Label
        o.Text = "Label" & i.ToString
        o.Dispose()
    Next
End Sub

Private Const Limit As Integer = 1024 * 1024

答案 3 :(得分:2)

我个人的感觉是,这里没有什么区别(超出明显的范围界限)。

其中任何一个都会分配新的内存并引用它,并使前一个内存少一个参考,准备好GC / as(如果需要)。

我不认为2会等待GC,部分是因为GC不是“内联”,部分是因为无法保证item是该实例的唯一引用并且无论如何都需要GC - 正如我所说,引用计数将会减少,就是这样。

当然这只是我对它的感觉,我会有​​兴趣看看其他人有什么要说的(我可以在写作中感受到一些博客文章!)

答案 4 :(得分:1)

当我保持//do some stuff为空时,两个版本都生成完全相同的IL,因此不存在任何可能的差异。我认为如果你在那里放一些代码,生成的IL仍然是相同的。

关于GC,第二个示例可能看起来理论上可能更快,因为GC可以在执行循环的增量和比较之前收集ListItem。但事实上,它也可以在第一种情况下做同样的事情,所以没有区别。

两个版本不相同的一个例外是使用闭包时。在第一个版本中,只有一个变量“实例”被关闭,在第二个案例中,有32个。因此存在性能差异(在第二种情况下,为每次迭代创建闭包对象),但意义上的差异更为重要。

一般来说,我喜欢尽可能保持变量,因为我认为它有助于读取。