当与垃圾收集语言一起使用时,哪种代码更 CPU/memory 高效?

Which code is more CPU/memory efficient when used with a Garbage Collected language?

我有这两段虚拟代码(假设它们是用 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 的效率更高。我说的对吗?

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

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

几点:

  • int(和其他原语)永远不会在堆上分配。它们直接存在于线程堆栈中,"allocation" 和 "deallocation" 是指针的简单移动,并且发生一次(当函数进入时,紧接在 return 之后),与作用域无关。

  • 经常访问的基元通常存储在寄存器中以提高速度,同样,无论范围如何。

  • 在你的情况下 a(可能 b 以及整个循环)将是 "optimized away",优化器足够聪明检测变量值发生变化但从未被读取的情况,并跳过冗余操作。或者,如果有代码实际查看 a,但不修改它,它可能会被优化器替换为常量值“10”,它只会在 [=10] 的任何地方内联显示=] 被引用。

  • New objects(如果你做了类似 String a = new String("foo") 的事情而不是 int)是 always 分配在年轻一代, 并且只有在他们活过几个小 collections 之后才会被转移到老一代。这意味着,在大多数情况下,当一个 object 被分配在函数内部,并且从不从外部引用时,无论其确切范围如何,它都永远不会进入老一代,除非你的堆结构迫切需要调整。

  • 正如评论中指出的那样,有时 VM 可能会决定直接在老一代中分配一个大的 object(java 也是如此,不仅仅是.net),所以上面的观点只适用于大多数情况,而不是always。然而,关于这个问题,这没有任何区别,因为如果决定在老一代中分配一个 object,无论如何它都是在不考虑其初始引用范围的情况下做出的。

从性能和内存的角度来看,您的两个代码段是相同的。但是从可读性的角度来看,在尽可能窄的范围内声明所有变量总是一个好主意。