【问题标题】:.NET pre-allocating memory vs ad-hoc allocation.NET 预分配内存与临时分配
【发布时间】:2011-04-28 05:48:57
【问题描述】:

我正在开发一些需要极低延迟并推送大量内存的应用程序,并且正在做一些测试,例如临时分配列表与预先分配和清除列表执行。 我期待预分配内存的测试运行速度更快,但令我惊讶的是,它们实际上稍微慢了一点(当我让测试运行 10 分钟时,平均差异约为 400 毫秒)。

这是我使用的测试代码:

    class Program
{
    private static byte[] buffer = new byte[50];
    private static List<byte[]> preAlloctedList = new List<byte[]>(500);

    static void Main(string[] args)
    {
        for (int k = 0; k < 5; k++)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();

            for (int i = 0; i < 1000000; i++)
            {
                List<byte[]> list = new List<byte[]>(300);

                for (int j = 0; j < 300; j++)
                {
                    list.Add(buffer);
                }
            }

            sw.Stop();
            Console.WriteLine("#1: " + sw.Elapsed);
            sw.Reset();
            sw.Start();

            for (int i = 0; i < 1000000; i++)
            {
                for (int j = 0; j < 300; j++)
                {
                    preAlloctedList.Add(buffer);
                }

                preAlloctedList.Clear();
            }
            sw.Stop();
            Console.WriteLine("#2: " + sw.Elapsed);
        }

        Console.ReadLine();
    }
}

现在,真正有趣的是,我并排运行 perfmon 并看到以下模式,看起来与我预期的一样:

绿色 = 第 0 代系列
蓝色 = 分配的字节数/秒
红色 = % GC 时间

下面的控制台应用程序显示了 #1 和 #2 的测试运行时

所以,我的问题是,为什么测试 #1 比 #2 快?
显然,我宁愿在我的应用程序中使用 Test #2 的 perfmon 统计信息,因为基本上没有内存压力、没有 GC 收集等,但 #1 似乎稍微快一些?
List.Clear() 会带来那么多开销吗?

谢谢,

汤姆

编辑 我做了另一个测试,使用相同的设置,但在启用服务器 GC 的情况下运行应用程序,现在 #2 变得稍微快一些

【问题讨论】:

    标签: .net memory garbage-collection


    【解决方案1】:

    List.Clear() 会带来那么多开销吗?

    是的,与(单个)GC.Collect(0) 相比,对Clear() 进行数千次调用可能会更慢。

    据我所知,dotNet 内存系统在分配/释放短期内存块方面非常快。

    但是要小心地将这个简单测试的结论带到您的实际应用中。

    【讨论】:

    • 我查看了 List 实现,并且 Clear() 进行了外部调用。你碰巧知道那是什么吗?我可以用 Clear() 实现 List 只重置大小字段
    • 没关系,这就是 Clear 所做的:将 Array 中的一系列元素设置为零、false 或 null,具体取决于元素类型。
    • @Tom:你必须清除数组,否则你会让所有的元素都变成根,所以 GC 无法收集它们。一般来说,我会尽量避免像这样重新发明轮子......
    【解决方案2】:

    我怀疑测试#1 更快的原因是垃圾收集发生在单独的线程上,并且分配的开销低于额外的List&lt;T&gt;.Clear 调用。由于这些列表都不是很大(每个只有 300 个引用),而且它们都是在一个紧密的循环中创建和无根的,所以它们通常都留在第 0 代。

    我在过去的分析过程中注意到了这一点 - 重用 List&lt;T&gt; 并在其上调用 Clear 通常比重新分配要慢。 Clear() 实际上清除了内部数组并重置了列表的参数,我认为这比列表的初始分配有(稍微)更多的开销。

    但是,在我看来,这个例子实际上只是表明 .NET 中的 GC 非常非常高效。

    【讨论】:

    • 感谢您的回复,这是有道理的。在非常相似的生产场景中,您会选择哪个选项?现在,非常有趣的是,当我在启用服务器 GC 的情况下运行相同的应用程序时,#2 实际上变得更快。我更新了帖子并附上了截图
    • @Tom:服务器模式以更高的延迟为代价提高了整体吞吐量。哪个更好取决于你。话虽如此,我通常只在我的生产环境中使用 #1 - 代码更干净,而且总体上表现更好。
    猜你喜欢
    • 2014-04-20
    • 2013-09-11
    • 1970-01-01
    • 1970-01-01
    • 2016-06-19
    • 1970-01-01
    • 1970-01-01
    • 2022-09-24
    • 2014-07-17
    相关资源
    最近更新 更多