C# 列表泛型类的性能

C# 列表泛型类的性能,c#,.net,performance,list,boxing,C#,.net,Performance,List,Boxing,我试着检查有拳击和没有拳击的表演。 下面是一个代码: public struct p1 { int x, y; public p1(int i) { x = y = i; } } public class MainClass { public static void Main() { var al = new List<object>(); var l = new List<p1&

我试着检查有拳击和没有拳击的表演。 下面是一个代码:

public struct p1
{
    int x, y;
    public p1(int i)
    {
        x = y = i;
    }

}
public class MainClass
{
    public static void Main()
    {
        var al = new List<object>();
        var l = new List<p1>();
        var sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < 1000; i++)
        {
            al.Add(new p1(i));
        }
        p1 b;
        for (int i = 0; i < 1000; i++)
        {
            b = (p1)al[i];
        }
        Console.WriteLine(sw.ElapsedTicks);

        var time = sw.ElapsedTicks;

        for (int i = 0; i < 1000; i++)
        {
            l.Add(new p1(i));
        }
        p1 v;
        for (int i = 0; i < 1000; i++)
        {
            v = l[i];
        }
        var t = sw.ElapsedTicks - time;
        Console.WriteLine(t);
        Console.ReadKey();
    }
}
公共结构p1
{
int x,y;
公共p1(内部i)
{
x=y=i;
}
}
公共类主类
{
公共静态void Main()
{
var al=新列表();
var l=新列表();
var sw=新秒表();
sw.Start();
对于(int i=0;i<1000;i++)
{
新增(新p1(i));
}
p1b;
对于(int i=0;i<1000;i++)
{
b=(p1)al[i];
}
控制台写入线(软件ElapsedTicks);
var时间=sw.ElapsedTicks;
对于(int i=0;i<1000;i++)
{
l、 增加(新的p1(i));
}
p1v;
对于(int i=0;i<1000;i++)
{
v=l[i];
}
var t=sw.ElapsedTicks-时间;
控制台写入线(t);
Console.ReadKey();
}
}
但对象列表比p1列表工作得更快。为什么?
1139 9256
1044
6909

我怀疑这可能是由很多因素造成的

首先,您应该始终将这样的计时放入循环中,并在同一流程中多次运行它们。JIT开销将发生在第一次运行时,并且可以控制您的计时。这将扭曲结果。(通常,您希望完全忽略第一次运行…)

另外,请确保您正在发布版本中运行此功能,并在VisualStudio测试主机之外运行。(不要按F5键-使用Ctrl+F5或从外部运行VS。)否则,测试主机将禁用大多数优化并显著降低结果的速度

例如,请尝试以下操作:

public static void Main()
{
    for (int run = 0; run < 4; ++run)
    {
        if (run != 0)
        {
            // Ignore first run
            Console.WriteLine("Run {0}", run);
        }
        var al = new List<object>();
        var l = new List<p1>();
        var sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < 1000; i++)
        {
            al.Add(new p1(i));
        }
        p1 b;
        for (int i = 0; i < 1000; i++)
        {
            b = (p1)al[i];
        }
        sw.Stop();
        if (run != 0)
        {
            // Ignore first run
            Console.WriteLine("With boxing: {0}", sw.ElapsedTicks);
        }

        sw.Reset();
        sw.Start();
        for (int i = 0; i < 1000; i++)
        {
            l.Add(new p1(i));
        }
        p1 v;
        for (int i = 0; i < 1000; i++)
        {
            v = l[i];
        }
        sw.Stop();
        if (run != 0)
        {
            // Ignore first run
            Console.WriteLine("Without boxing: {0}", sw.ElapsedTicks);
        }
    }
    Console.ReadKey();
}
这显然比一般的非盒装版本要好得多


考虑到结果中的大量数据-我怀疑此测试是在VS调试模式下运行的…

结构是通过值而不是引用传递的。当你自动装箱时,我相信它会被引用。因此,它在第二个循环中被复制了多次。

作为旁白,请使用更好的变量和类名,即使在示例代码中也是如此。当遇到代码墙时,当出现像p1、al、l、sw、b、v和t这样的名称时,并不总是清楚代码在做什么。我刚刚尝试了你的代码,得到了非常不同的结果-第二个是3-5倍大的数字。所以,这可能是因为您的测试配置,正如里德所建议的那样……有趣的是,即使您修改了代码,我的第二个循环也会变慢。@安德鲁:每次运行,还是第一次运行?对我来说,第一次跑步的速度也比较慢。。。但不是后来的。(我怀疑这是创建列表时的JIT开销)我收回这一点。我的代码与你的不同,但是当我在循环中运行我的代码时,第二组代码仍然较慢。我正在浏览你和我的版本,看看为什么会出现这些结果。在我所做的版本中,第二组总是报告说即使在第一次运行后速度也会变慢,但在你的版本中,第二组报告速度更快。显然有一些重大的变化,我现在正在看。好吧,我没有重置秒表是一个愚蠢的错误。因此,JIT确实会处理后续循环。它在两个循环中都会被复制多次—但是,在第一个循环中,每个存储都需要装箱操作,每次访问都需要将值解除装箱,然后复制到结果中。第一个肯定较慢(请参阅我的计时答案)。@Reed这可能是我对装箱和列表的实现方式的误解,但如果T是一个值类型,则它会在Add()中为使用的每个方法传递值。如果已装箱,则装箱引用不应在初始装箱后生成新副本。如果List.Add did List.CheckIfExists(t)、List.AddForReal(t),则会产生3个副本。如果它被装箱,我不相信它会这样发生。当您传递引用类型时,每个方法的引用都会被复制(按值)。也就是说,装箱开销远远大于值类型副本的开销。此外,JIT通常会内联并消除传递给方法的值类型的副本,在某些情况下。。。顺便说一句,列表不检查是否存在,因为它允许重复-所以唯一的检查是检查它是否需要增长,然后将副本复制到内部数组中。
Run 1
With boxing: 99
Without boxing: 61
Run 2
With boxing: 92
Without boxing: 56
Run 3
With boxing: 97
Without boxing: 54