【发布时间】:2018-02-15 14:05:22
【问题描述】:
是的,我知道 如何 使用 GC.SuppressFinalize() - 它已解释为 here。我已经读过很多次了,使用GC.SuppressFinalize() 会从终结队列中删除对象,并且认为这很好,因为它使 GC 从调用终结器的额外工作中解脱出来。
所以我制作了这个(大部分没用的)代码,其中类实现了IDisposable,如链接到答案中:
public class MyClass : IDisposable
{
~MyClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
System.Threading.Thread.Sleep(0);
disposed = true;
}
}
}
这里我使用Sleep(0) 来模仿一些简短的非托管工作。请注意,由于类中的布尔字段,这个非托管工作永远不会执行超过一次 - 即使我多次调用 Dispose() 或者如果对象首先被释放然后完成 - 在任何这些情况下,“非托管工作”是只执行一次。
这是我用于测量的代码:
var start = DateTime.UtcNow;
var objs = new List<Object>();
for (int i = 0; i < 1000 * 1000 * 10; i++)
{
using (var obj = new MyClass())
{
objs.Add(obj);
}
}
objs = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
var duration = (DateTime.UtcNow - start).TotalMilliseconds;
Console.WriteLine(duration.ToString());
是的,我将刚刚处理的对象添加到 List。
所以我运行上面的代码,它在 12.01 秒内运行(发布,没有调试器)。然后我注释掉 GC.SuppressFinalize() 调用并再次运行代码,它在 13.99 秒内运行。
调用GC.SuppressFinalize() 的代码快了14.1%。即使在这种荒谬的情况下,所有的一切都是为了给 GC 施加压力(你很少会在行中使用终结器来制作一千万个对象,不是吗?)差异大约是 14%。
我猜在现实场景中,只有一小部分对象首先具有终结器,并且这些对象也没有大规模创建,因此整体系统性能的差异可以忽略不计。
我错过了什么吗?有没有一个现实的场景,我会看到使用 GC.SuppressFinalize() 的显着好处?
【问题讨论】:
-
除非您测量到对象的处理是您代码中的瓶颈,否则我只会使用标准
IDisposable模式并相信 MS 已在核心中为您完成了相关优化代码库。对我来说,这在 90% 的时间里都是过早的优化。优化应该始终基于测量,盲目地无缘无故地遵循模式是万恶之源。 -
@Liam 嗯,这就是我要问的。在上面的代码中,无论是否调用
SuppressFinalize(),处置的“有用”部分都会为每个对象运行一次。什么情况下我会看到重大差异? -
有趣的是,链接的问题现在已经有将近 10 年的历史了。自从提出要求后,框架中发生了很多变化
-
它在终结器线程上额外运行了 1000 万次 Sleep(0)。为什么这需要超过一秒的时间并不明显,内核调用的十分之一微秒并不是不典型的。 Sleep(0) 的实际行为是高度不可预测的,如果有另一个线程准备好运行,它将让处理器。不是你想在性能测量中使用的东西,太随机了。终结器更典型的性能抢夺行为是非托管内存中的硬页面错误。
-
你为什么要使用一次性反模式?如果您直接拥有非托管资源,则需要 SafeHandle。如果您间接拥有它们,您希望
Dispose没有终结器。只有一些罕见的特殊场景需要传统的终结器(主要是调试/日志记录)。
标签: c# .net garbage-collection idisposable suppressfinalize