2015-01-09 64 views
5

我的代码,这两个虚拟件(让我们来看看他们都写在Java和C#中,所有变量本地):与垃圾收集语言一起使用时,哪种代码的CPU /内存效率更高?

代码1:

int a; 
int b = 0; 

for (int i = 1; i < 10 ; i++) 
{ 
    a = 10; 
    b += i; 

    // a lot of more code that doesn't involve assigning new values to "a" 
} 

代码2:

int b = 0; 

for (int i = 1; i < 10 ; i++) 
{ 
    int a = 10; 
    b += i; 

    // a lot of more code that doesn't involve assigning new values to "a" 
} 

乍一看,我会说这两个代码都消耗相同数量的内存,但代码1的CPU效率更高,因为它只创建和分配变量a一次。 然后我读了垃圾收集器是非常有效的,以至于代码2会更有效地存储内存(和CPU):在循环内部保留变量a使它属于Gen0,所以它将在变量b之前被垃圾收集。

因此,与垃圾收集语言一起使用时,代码2效率更高。我对吗?

+58

这里没有垃圾回收。 –

+3

重复分配'a'有什么意义?还要注意,这两位有很大不同(第二位在循环外没有'a'变量,第一位是)。 –

+8

@ T.J.Crowder显然它只是一个玩具的例子;一般的想法是将变量作用在循环中是否有意义,或者是否应该将它们拉出循环之外(即使不在循环外部使用)作为优化。 – Servy

回答

38

的几点:

  • 整数(和其他原语)是从未被分配过的堆。它们直接在线程堆栈上生存,“分配”和“释放”是指针的简单移动,并且只发生一次(当函数被输入时,并且在返回之后),而不管范围如何。

  • 经常访问的基元通常存储在寄存器中,以便速度,而不管范围如何。

  • 您的情况a(也可能是b以及整个循环)将被“优化”,优化器足够智能以在变量值发生变化时检测情况,但永远不会读取,并跳过冗余操作。或者,如果代码实际上看起来是a,但没有修改它,那么它可能会被优化器替换为常量值“10”,该值将在任何引用a的位置出现。

  • 的新对象(如果你不喜欢的东西String a = new String("foo")例如不是int)是总是在年轻一代分配的,之后他们生存的一些小的藏品只得到转移到老根。这意味着,对于大多数情况,当一个对象被分配到一个函数内部,并且从不从外部引用时,除非堆结构迫切需要调整,否则它将永远不会将其传递给旧代。

  • 正如在评论中指出的那样,虚拟机有时可能决定直接在旧版本中分配一个大对象(这对Java也是如此,而不仅仅是.net),所以上面的几点仅适用于大多数情况,但不是总是。但是,就这个问题而言,这并没有什么区别,因为如果决定在旧有物体中分配一个物体,无论如何都不考虑其初始参考的范围。

从性能和记忆的角度来看,你的两个片段是相同的。尽管如此,从可读性的角度来看,将所有变量声明在尽可能小的范围内总是一个好主意。

+0

对于GC的当前.NET实现和大对象将分配在大对象堆上,这是有效的Gen2(最高一代)。所以分配并不总是最年轻的一代。 – CodesInChaos

+0

@CodesInChaos这是一个很好的观点(对于Java也是如此,而不仅仅是.net)。感谢您指出!我已经更新了答案以反映这一点。 – Dima

+0

@CodesInChaos:不,LOH与Gen2实际上并不相同。 LOH根本不是压实过程的一部分。 –

18

在片段2中的代码实际执行之前,它最终会变成看起来像片段1后面的代码(无论是编译器还是运行时)。因此,这两个片段的性能将会相同,因为它们在某些时候会在功能上编译相同的代码。

请注意,对于非常短暂的变量,它们实际上很可能没有为它们分配内存。它们可能完全存储在一个寄存器中,涉及0个内存分配。

+1

编译器还是jit? – TheLostMind

+1

@TheLostMind是的,在堆栈中的某个地方。为了这个问题的目的,它应该不太重要。 – Servy

+1

我同意,它是怎么发生的*无关紧要*并且从分发到分发的变化:) – TheLostMind