我有两个虚拟代码片段(让我们考虑它们是用Java或C#编写的,所有变量都是本地的):
代码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效率更高。我是对的吗?
答案 0 :(得分:40)
几点:
ints(和其他原语)永远不会在堆上分配。他们直接生活在线程堆栈上,&#34;分配&#34;和#34; deallocation&#34;是一个简单的指针移动,并发生一次(当输入函数时,并在返回后立即),无论范围如何。
原语通常存储在寄存器中以提高速度,无论范围如何。
在你的情况下a
(可能还有b
以及整个循环)将被优化掉#34;,优化器足够智能化检测变量值更改但永远不会读取的情况,并跳过冗余操作。或者,如果存在实际查看a
的代码,但未对其进行修改,则优化程序可能会将其替换为&#34; 10&#34;的常量值,这只是&#39; ll在任何引用a
的地方显示内联。
新对象(如果您执行类似String a = new String("foo")
而不是int的操作)始终在年轻一代中分配,并且只有在它们存活后才会转移到旧的gen中几个小集合。这意味着,对于大多数情况,当一个对象在函数内部分配,并且从不从外部引用时,它将永远不会使它成为旧的,无论其确切的范围如何,除非您的堆结构迫切需要调整。
正如评论中所指出的,有时VM可能决定直接在旧版本中分配一个大对象(对于java也是如此,而不仅仅是.net),所以上述观点仅适用于大多数案件,但不总是。但是,就这个问题而言,这并没有任何区别,因为如果决定在旧代中分配一个对象,那么无论如何都不考虑其初始引用的范围。
从性能和内存的角度来看,您的两个片段是完全相同的。但从可读性的角度来看,在尽可能窄的范围内声明所有变量总是一个好主意。
答案 1 :(得分:18)
在片段2中的代码实际执行之前,它最终会被转换为看起来像幕后片段1中的代码(无论是编译器还是运行时)。因此,两个片段的性能将是相同的,因为它们在某些时候会在功能上编译成相同的代码。
请注意,对于非常短暂的变量,它们实际上很可能根本没有为它们分配内存。它们可能完全存储在寄存器中,涉及0个内存分配。