列表性能与 ArrayList 内存分配性能

List perfomance vs ArrayList memory allocation performance

我有以下代码:

namespace ConsoleCodeGenerator
{
    internal class Foo
    {
        public double F { get; set; }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            //int size = 100000;
            int size = 70000000;

            List<Foo> list = new List<Foo>(size);
            ArrayList arrayList = new ArrayList(size);

            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < size; i++)
            {
                Foo f = new Foo();
                f.F = i;
                list.Add(f);
            }
            sw.Stop();
            Console.WriteLine("List: {0}", sw.ElapsedMilliseconds);

            Stopwatch sw2 = new Stopwatch();
            sw2.Start();
            for (int i = 0; i < size; i++)
            {
                Foo f = new Foo();
                f.F = i;
                arrayList.Add(f);
            }
            sw2.Stop();
            Console.WriteLine("arrayList: {0}", sw2.ElapsedMilliseconds);
        }
    }
}

如果我使用 int size = 100000;然后 List 以 2:6 毫秒的比例优于 ArrayList。但是如果让 size = 70000000;然后 ArrayList 在我的电脑上有更好的性能 5450:4809。看起来处理巨大的(大约数百万个项目)ArrayList 可能比 List 更快。为什么 boxing/unboxing 对小内存分配很重要,而对大数组无关紧要

你的误会比这还深一点。

首先,制定一个好的基准很难 - 你的不好。

其次,仅在 值类型 上进行装箱 - 您在这两种情况下都添加了 class,因此即使 ArrayList 也不会发生装箱。事实上,通过将 double 包装在 class 中,您只是手动 装箱 值 - 这就是装箱的意思(当然,IL box / unbox 指令可能更有效)。尝试直接插入 double,您会发现巨大的差异。

为了进一步说明基准测试问题,您完全忽略了内存分配(和收集)模式。当您自己预分配数组时(这就是容量参数的用途),您并没有预分配对象 (Foo)。例如,这对于结构或 doubles 并不重要,但在这种情况下,您只是将所有内存压力推入相关周期。

List 只要不再在方法中使用就可以收集,因此 ArrayList 将在需要收集时立即获得空闲的预先准备好的内存。所以即使是测试的顺序也会有很小的不同。

最后,您需要可重复性 - 使用 List 进行一百次测试,使用 ArrayList 进行另外一百次测试,尽可能多地进行隔离。并且不要忘记预热基准测试以消除初始化时间。

您可以找到很多关于在 C# 中制作像样的基准测试的信息。真的不容易。