GC 渴望根集合

Bac*_*ave 5 .net c# garbage-collection

在Pro .NET 性能 - 优化您的 C# 应用程序的第 96 页上,它讨论了 GC eager root 集合:

对于每个局部变量,JIT 将最早和最新指令指针的地址嵌入到一个表中,其中该变量仍然作为根相关。然后,GC 在执行堆栈遍历时使用这些表。

然后它提供了这个示例:

    static void Main(string[] args)
    {
        Widget a = new Widget();
        a.Use();
        //...additional code
        Widget b = new Widget();
        b.Use();
        //...additional code
        Foo(); //static method
    }
Run Code Online (Sandbox Code Playgroud)

然后它说:

上述讨论意味着将代码分解为更小的方法并使用更少的局部变量不仅仅是一个好的设计措施或软件工程技术。使用 .NET GC,它还可以提供性能优势,因为您的本地根更少!这意味着编译方法时 JIT 的工作量更少,根 IP 表占用的空间更少,GC 执行堆栈遍历时的工作量也更少。

我不明白将代码分解成更小的方法会有什么帮助。我将代码分解为:

    static void Main(string[] args)
    {
        UseWidgetA();
        //...additional code
        UseWidgetB();
        //...additional code
        Foo(); //static method
    }

    static void UseWidgetA()
    {
        Widget a = new Widget();
        a.Use();
    }

    static void UseWidgetB()
    {
        Widget b = new Widget();
        b.Use();
    }
}
Run Code Online (Sandbox Code Playgroud)

本地根源较少:

为何本土根基较少?局部根的数量仍然相同,每个方法中有一个局部根。

编译方法时 JIT 的工作量更少:

当然,这会让事情变得更糟,因为这 2 个额外的方法需要 2 个额外的表。JIT 还仍然需要记录最早和最新的指令指针,其中变量在每个方法中仍然相关,但它只是有更多的方法来做到这一点。

执行堆栈遍历时 GC 的工作量更少:

拥有更多更小的方法如何意味着在堆栈遍历期间 GC 的工作量更少?

Kon*_*osa 5

萨沙不关心我,但让我谈谈我的两分钱。

\n\n

首先,我将其视为一条通用规则 - 当您将一个方法拆分为更小的方法时,某些部分可能不需要进行 JIT 处理,因为某些子例程是有条件执行的。

\n\n

其次,JITting 确实会生成有关实时堆栈根的所谓GC 信息。更大的方法,更大的GC 信息理论上来说,在 GC 期间解释它的成本也应该更大,但是,通过将 GC 信息分割成块可以克服这个问题。然而,有关堆栈根活跃度的信息仅针对所谓的安全点存储存储。有两种方法:

\n\n
    \n
  • 部分可中断- 唯一的安全点是在调用其他方法期间。这使得方法不太“可挂起”,因为运行时需要等待这样的安全点来挂起方法,但为 GC 信息消耗更少的内存。
  • \n
  • 完全可中断- 方法的每条指令都被视为安全点,这显然使方法非常“可暂停”,但需要大量存储(数量与代码本身类似)
  • \n
\n\n

正如《运行时之书》中所述:\xe2\x80\x9cJIT 根据启发式选择是否发出完全或部分\n可中断代码,以找到代码质量、\nGC 信息大小和 GC 挂起延迟之间的最佳权衡.\xe2\x80\x9d

\n\n

在我看来,较小的方法有助于 JIT 做出更好的决策(基于其启发式),使方法部分或完全可中断。

\n