【问题标题】:Perfomance of List generic classList泛型类的性能
【发布时间】:2011-01-06 19:07:19
【问题描述】:

我试图检查拳击和不拳击的表现。 这是一个代码:

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 列表工作得更快。为什么?
1139 9256
1044 6909

【问题讨论】:

  • 顺便说一句,请使用更好的变量和类名,即使在示例代码中也是如此。当遇到一堵代码墙并出现像p1, al, l, sw, b, v, &amp; t 这样的名称时,代码在做什么并不总是很清楚。
  • 我刚刚尝试了你的代码,得到了非常不同的结果——第二个是 3-5 倍大的数字。因此,正如 Reed 建议的那样,原因可能在于您的测试配置......

标签: c# .net performance list boxing


【解决方案1】:

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

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

另外,请确保您在发布版本中运行它,在 Visual Studio 测试主机之外运行。 (不要按 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();
}

在我的系统上,通过忽略第一次运行(JIT 问题),并在发布时在 VS 测试主机之外运行,我得到:

Run 1
With boxing: 99
Without boxing: 61
Run 2
With boxing: 92
Without boxing: 56
Run 3
With boxing: 97
Without boxing: 54

这对于通用的非盒装版本显然要好得多。

鉴于您的结果中的数字非常大 - 我怀疑此测试是在 VS 中以调试模式运行的...

【讨论】:

  • 有趣的是,即使您修改了代码,第二个循环对我来说也更慢。
  • @Andrew:每次运行,还是第一次运行?第一次运行对我来说也比较慢......但不是后续的。 (我怀疑这是 List 创建的 JIT 开销)
  • 我收回了。我的代码与您的不同,但是当我循环运行我的代码时,第二组仍然较慢。我正在查看您的版本和我的版本,以了解为什么会出现这些结果。在我做的版本中,即使在第一次运行之后,第二组总是报告速度变慢,但你的第二组报告速度更快。显然有一些重大变化,我现在正在查看。
  • 好吧,我没有重置秒表是个愚蠢的错误。所以 JIT 确实会处理后续循环。
【解决方案2】:

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

【讨论】:

  • 它在两个循环中都被复制了多次——但是,首先,每个存储都需要一个装箱操作,每次访问都需要将值拆箱,然后复制到结果中。第一个肯定更慢(请参阅我的时间回答)。
  • @Reed 这可能是我对装箱以及如何实现 List 的误解,但如果 T 是值类型,那么它在 Add() 中通过值传递给它使用的每个方法。如果它是装箱的,那么装箱的参考不应在初始装箱后产生新的副本。如果 List.Add 执行 List.CheckIfExists(t)、List.AddForReal(t),则会导致 3 个副本发生。如果它被装箱,我不相信它会以这种方式发生。
  • 当您传递引用类型时,会为每个方法复制引用(按值)。话虽如此,装箱开销远大于值类型副本的开销。此外,JIT 通常会内联并消除传递给方法的值类型的副本,在某些情况下...... List 不会检查是否存在,顺便说一句,因为它允许重复 - 所以唯一的检查是检查它是否需要增长,然后复制到内部数组中。
猜你喜欢
  • 1970-01-01
  • 2015-01-26
  • 1970-01-01
  • 1970-01-01
  • 2013-08-13
  • 1970-01-01
  • 2011-10-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多