我有以下代码:
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;然后按比例2:6毫秒列出outperfoms ArrayList。但是如果要制作尺寸= 70000000;然后ArrayList在我的电脑上有更好的性能5450:4809。它看起来像处理巨大(大约数百万项)ArrayList可能比List更快。为什么装箱/拆箱在小内存分配时很重要,而在大阵列无关紧要
答案 0 :(得分:4)
你的误解比这更深刻。
首先,制定一个好的基准很难 - 你的不好。
其次,装箱仅发生在值类型上 - 您在两种情况下都添加了一个类,因此即使使用ArrayList
也不会发生装箱。事实上,通过将double
包装在一个类中,您只需手动装箱该值 - 这就是拳击意味着什么(当然,IL {{1 } / box
指令可能更有效率)。尝试直接插入unbox
,您就会看到巨大的差异。
为了扩展基准测试问题,您完全忽略了内存分配(和集合)模式。虽然您自己预先分配了数组(这是容量参数的用途),但您并未预先分配对象(double
)。例如,对于结构或Foo
s,这不重要,但在这种情况下,您只需将所有内存压力推入相关周期。
double
一旦在方法中不再使用,就可以收集List
,因此ArrayList
只要需要,就会获得免费的,预先准备好的内存采集。因此,即使是测试的顺序也会产生很小的差异。
最后,您需要重复性 - 使用List
进行一百次测试,使用ArrayList
进行一百次测试,并尽可能地隔离。并且不要忘记预热基准以摆脱初始化时间。
您可以在C#中找到有关制作体面基准的大量信息。这真的不容易。