繁体   English   中英

数组与列表的性能

[英]Performance of Arrays vs. Lists

假设您需要一个需要经常迭代的整数列表/数组,我的意思是非常频繁。 原因可能会有所不同,但可以说它是大容量处理的最内部循环的核心。

一般来说,由于大小的灵活性,人们会选择使用列表(List)。 最重要的是,msdn 文档声称列表在内部使用了一个数组,并且应该同样快速地执行(快速浏览一下 Reflector 就证实了这一点)。 然而,这涉及到一些开销。

有人真的测量过这个吗? 在列表中迭代 6M 次是否会与数组花费相同的时间?

非常容易测量...

我知道长度是固定的少数紧密循环处理代码中,我使用数组进行额外的微小优化; 如果您使用索引器 / 用于表单,数组可能会稍微快一些 - 但 IIRC 认为这取决于数组中的数据类型。 但除非您需要进行微优化,否则请保持简单并使用List<T>等。

当然,这只适用于读取所有数据的情况; 对于基于键的查找,字典会更快。

这是我使用“int”的结果(第二个数字是校验和,以验证它们都做了相同的工作):

(已编辑以修复错误)

List/for: 1971ms (589725196)
Array/for: 1864ms (589725196)
List/foreach: 3054ms (589725196)
Array/foreach: 1860ms (589725196)

基于测试台:

using System;
using System.Collections.Generic;
using System.Diagnostics;
static class Program
{
    static void Main()
    {
        List<int> list = new List<int>(6000000);
        Random rand = new Random(12345);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next(5000));
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}

简短的回答:

在 .Net 中, List<T>Array<T>具有相同的速度/性能,因为在 .Net 中, List是 Array 的包装器。

再说一遍: List里面是一个Array 在 .Net List<T>是来自其他语言的ArrayList<T>


详细说明您需要在哪些情况下使用:

  • 数组需要使用:

    • 尽可能经常。 它速度很快,并且对于相同数量的信息占用最小的 RAM 范围。
    • 如果您知道所需细胞的确切数量
    • 如果保存在数组中的数据 < 85000 b(整数数据为 85000/32 = 2656 个元素)
    • 如果需要高随机存取速度
  • 清单需要使用:

    • 如果需要将单元格添加到列表末尾(经常)
    • 如果需要在列表的开头/中间添加单元格(不经常)
    • 如果保存在数组中的数据 < 85000 b(整数数据为 85000/32 = 2656 个元素)
    • 如果需要高随机存取速度
  • LinkedList 需要使用:

    • 如果需要在列表的开头/中间/结尾添加单元格(经常)

    • 如果需要仅顺序访问(向前/向后)

    • 如果您需要保存 LARGE 项目,但项目数量很少。

    • 最好不要用于大量项目,因为它会为链接使用额外的内存。

      如果您不确定是否需要 LinkedList - 您不需要它。

      只是不要使用它。


更多细节:

颜色含义

数组 vs 列表 vs 链表

更多细节:

https://stackoverflow.com/a/29263914/4423545

我认为性能会非常相似。 使用列表与数组时所涉及的开销是,恕我直言,当您将项目添加到列表时,以及当列表必须增加它在内部使用的数组的大小时,当达到数组的容量时。

假设您有一个容量为 10 的列表,那么一旦您要添加第 11 个元素,该列表将增加其容量。 您可以通过将列表的容量初始化为它将容纳的项目数来降低对性能的影响。

但是,为了确定迭代 List 是否与迭代数组一样快,为什么不对其进行基准测试呢?

int numberOfElements = 6000000;

List<int> theList = new List<int> (numberOfElements);
int[] theArray = new int[numberOfElements];

for( int i = 0; i < numberOfElements; i++ )
{
    theList.Add (i);
    theArray[i] = i;
}

Stopwatch chrono = new Stopwatch ();

chrono.Start ();

int j;

 for( int i = 0; i < numberOfElements; i++ )
 {
     j = theList[i];
 }

 chrono.Stop ();
 Console.WriteLine (String.Format("iterating the List took {0} msec", chrono.ElapsedMilliseconds));

 chrono.Reset();

 chrono.Start();

 for( int i = 0; i < numberOfElements; i++ )
 {
     j = theArray[i];
 }

 chrono.Stop ();
 Console.WriteLine (String.Format("iterating the array took {0} msec", chrono.ElapsedMilliseconds));

 Console.ReadLine();

在我的系统上; 遍历数组需要 33 毫秒; 遍历列表需要 66 毫秒。

老实说,我没想到变化会这么大。 所以,我把我的迭代放在一个循环中:现在,我执行了两次迭代 1000 次。 结果是:

迭代列表耗时 67146 毫秒 迭代数组耗时 40821 毫秒

现在,变化不再那么大了,但仍然......

因此,我启动了.NET Reflector,List类的indexer的getter看起来是这样的:

public T get_Item(int index)
{
    if (index >= this._size)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException();
    }
    return this._items[index];
}

如您所见,当您使用 List 的索引器时,List 会检查您是否没有超出内部数组的范围。 这种额外的检查是有代价的。

如果您只是从任何一个中获取一个值(而不是在循环中),那么两者都进行边界检查(记住您在托管代码中)它只是列表执行了两次。 请参阅后面的注释,了解为什么这可能不是什么大问题。

如果您使用自己的 for(int int i = 0; i < x.[Length/Count];i++) 那么关键区别如下:

  • 大批:
    • 边界检查被移除
  • 列表
    • 执行边界检查

如果您使用的是 foreach,那么主要区别如下:

  • 大批:
    • 没有分配对象来管理迭代
    • 边界检查被移除
  • 通过已知为 List 的变量列出。
    • 迭代管理变量是堆栈分配的
    • 执行边界检查
  • 通过已知为 IList 的变量列出。
    • 迭代管理变量是堆分配的
    • 还执行边界检查列表值在 foreach 期间可能不会更改,而数组可以。

边界检查通常没什么大不了的(特别是如果你在一个具有深度管道和分支预测的 CPU 上——这几天的常态),但只有你自己的分析可以告诉你这是否是一个问题。 如果您在避免堆分配的部分代码中(很好的例子是库或哈希码实现),那么确保将变量键入为 List 而不是 IList 将避免这种陷阱。 与往常一样,如果重要的话。

[另见这个问题]

我已经修改了 Marc 的答案以使用实际的随机数,并且实际上在所有情况下都做了同样的工作。

结果:

for      foreach
Array : 1575ms     1575ms (+0%)
List  : 1630ms     2627ms (+61%)
         (+3%)     (+67%)

(Checksum: -1000038876)

在 VS 2008 SP1 下编译为 Release。 在 Q6600@2.40GHz、.NET 3.5 SP1 上运行而无需调试。

代码:

class Program
{
    static void Main(string[] args)
    {
        List<int> list = new List<int>(6000000);
        Random rand = new Random(1);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next());
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = arr.Length;
            for (int i = 0; i < len; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
        Console.WriteLine();

        Console.ReadLine();
    }
}

我担心其他答案中发布的基准仍然会给编译器留下优化、消除或合并循环的空间,所以我写了一个:

  • 使用不可预测的输入(随机)
  • 运行计算结果并打印到控制台
  • 每次重复修改输入数据

结果是直接数组的性能比访问包装在 IList 中的数组的性能高 250%:

  • 10 亿次阵列访问:4000 毫秒
  • 10 亿个列表访问:10000 毫秒
  • 1 亿次数组访问:350 毫秒
  • 1亿列表访问:1000毫秒

这是代码:

static void Main(string[] args) {
  const int TestPointCount = 1000000;
  const int RepetitionCount = 1000;

  Stopwatch arrayTimer = new Stopwatch();
  Stopwatch listTimer = new Stopwatch();

  Point2[] points = new Point2[TestPointCount];
  var random = new Random();
  for (int index = 0; index < TestPointCount; ++index) {
    points[index].X = random.NextDouble();
    points[index].Y = random.NextDouble();
  }

  for (int repetition = 0; repetition <= RepetitionCount; ++repetition) {
    if (repetition > 0) { // first repetition is for cache warmup
      arrayTimer.Start();
    }
    doWorkOnArray(points);
    if (repetition > 0) { // first repetition is for cache warmup
      arrayTimer.Stop();
    }

    if (repetition > 0) { // first repetition is for cache warmup
      listTimer.Start();
    }
    doWorkOnList(points);
    if (repetition > 0) { // first repetition is for cache warmup
      listTimer.Stop();
    }
  }

  Console.WriteLine("Ignore this: " + points[0].X + points[0].Y);
  Console.WriteLine(
    string.Format(
      "{0} accesses on array took {1} ms",
      RepetitionCount * TestPointCount, arrayTimer.ElapsedMilliseconds
    )
  );
  Console.WriteLine(
    string.Format(
      "{0} accesses on list took {1} ms",
      RepetitionCount * TestPointCount, listTimer.ElapsedMilliseconds
    )
  );

}

private static void doWorkOnArray(Point2[] points) {
  var random = new Random();

  int pointCount = points.Length;

  Point2 accumulated = Point2.Zero;
  for (int index = 0; index < pointCount; ++index) {
    accumulated.X += points[index].X;
    accumulated.Y += points[index].Y;
  }

  accumulated /= pointCount;

  // make use of the result somewhere so the optimizer can't eliminate the loop
  // also modify the input collection so the optimizer can merge the repetition loop
  points[random.Next(0, pointCount)] = accumulated;
}

private static void doWorkOnList(IList<Point2> points) {
  var random = new Random();

  int pointCount = points.Count;

  Point2 accumulated = Point2.Zero;
  for (int index = 0; index < pointCount; ++index) {
    accumulated.X += points[index].X;
    accumulated.Y += points[index].Y;
  }

  accumulated /= pointCount;

  // make use of the result somewhere so the optimizer can't eliminate the loop
  // also modify the input collection so the optimizer can merge the repetition loop
  points[random.Next(0, pointCount)] = accumulated;
}

不要试图通过增加元素数量来增加容量。

表现

List For Add: 1ms
Array For Add: 2397ms

    Stopwatch watch;
        #region --> List For Add <--

        List<int> intList = new List<int>();
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 60000; rpt++)
        {
            intList.Add(rand.Next());
        }
        watch.Stop();
        Console.WriteLine("List For Add: {0}ms", watch.ElapsedMilliseconds);
        #endregion

        #region --> Array For Add <--

        int[] intArray = new int[0];
        watch = Stopwatch.StartNew();
        int sira = 0;
        for (int rpt = 0; rpt < 60000; rpt++)
        {
            sira += 1;
            Array.Resize(ref intArray, intArray.Length + 1);
            intArray[rpt] = rand.Next();

        }
        watch.Stop();
        Console.WriteLine("Array For Add: {0}ms", watch.ElapsedMilliseconds);

        #endregion

这是使用字典 IEnumerable 的一个:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

static class Program
{
    static void Main()
    {
        List<int> list = new List<int>(6000000);

        for (int i = 0; i < 6000000; i++)
        {
                list.Add(i);
        }
        Console.WriteLine("Count: {0}", list.Count);

        int[] arr = list.ToArray();
        IEnumerable<int> Ienumerable = list.ToArray();
        Dictionary<int, bool> dict = list.ToDictionary(x => x, y => true);

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in Ienumerable)
            {
                chk += i;
            }
        }

        Console.WriteLine("Ienumerable/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in dict.Keys)
            {
                chk += i;
            }
        }

        Console.WriteLine("Dict/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);


        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }

        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);



        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in Ienumerable)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Ienumerable/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in dict.Keys)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Dict/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}

测量结果很好,但是你会得到明显不同的结果,具体取决于你在内部循环中所做的事情。 衡量自己的情况。 如果您使用的是多线程,那么仅此一项就很重要了。

实际上,如果您在循环中执行一些复杂的计算,那么数组索引器与列表索引器的性能可能会非常小,最终,这并不重要。

在一些简短的测试中,我发现两者的组合在我称之为合理密集的数学中更好:

类型: List<double[]>

时间:00:00:05.1861300

类型: List<List<double>>

时间:00:00:05.7941351

类型: double[rows * columns]

时间:00:00:06.0547118

运行代码:

int rows = 10000;
int columns = 10000;

IMatrix Matrix = new IMatrix(rows, columns);

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();


for (int r = 0; r < Matrix.Rows; r++)
    for (int c = 0; c < Matrix.Columns; c++)
        Matrix[r, c] = Math.E;

for (int r = 0; r < Matrix.Rows; r++)
    for (int c = 0; c < Matrix.Columns; c++)
        Matrix[r, c] *= -Math.Log(Math.E);


stopwatch.Stop();
TimeSpan ts = stopwatch.Elapsed;

Console.WriteLine(ts.ToString());

我真希望我们有一些一流的硬件加速矩阵类,比如 .NET 团队已经完成了System.Numerics.Vectors类!

C# 可能是最好的 ML 语言,在这方面还有更多的工作!

由于我有一个类似的问题,这让我快速入门。

我的问题更具体一点,“自反数组实现的最快方法是什么”

Marc Gravell 所做的测试显示了很多,但并不完全是访问时间。 他的时间安排还包括遍历数组和列表。 由于我还想出了我想测试的第三种方法,一个“字典”,只是为了比较,我扩展了 hist 测试代码。

首先,我使用常量进行测试,这给了我一定的时间,包括循环。 这是一个“裸露的”时间,不包括实际访问。 然后我通过访问主题结构进行测试,这给了我和“包括开销”的时间、循环和实际访问。

“裸”时间和“间接费用”时间之间的差异让我了解了“结构访问”时间。

但是这个时间有多准确呢? 在测试期间,windows 会为舒尔做一些时间切片。 我没有关于时间片的信息,但我认为它在测试期间均匀分布,大约为几十毫秒,这意味着时间的准确性应该在 +/- 100 毫秒左右。 有点粗略的估计? 无论如何,系统测量误差的来源。

此外,测试是在“调试”模式下完成的,没有进行优化。 否则编译器可能会改变实际的测试代码。

所以,我得到两个结果,一个是常量,标记为“(c)”,另一个是访问标记为“(n)”,差异“dt”告诉我实际访问需要多长时间。

这是结果:

          Dictionary(c)/for: 1205ms (600000000)
          Dictionary(n)/for: 8046ms (589725196)
 dt = 6841

                List(c)/for: 1186ms (1189725196)
                List(n)/for: 2475ms (1779450392)
 dt = 1289

               Array(c)/for: 1019ms (600000000)
               Array(n)/for: 1266ms (589725196)
 dt = 247

 Dictionary[key](c)/foreach: 2738ms (600000000)
 Dictionary[key](n)/foreach: 10017ms (589725196)
 dt = 7279

            List(c)/foreach: 2480ms (600000000)
            List(n)/foreach: 2658ms (589725196)
 dt = 178

           Array(c)/foreach: 1300ms (600000000)
           Array(n)/foreach: 1592ms (589725196)
 dt = 292


 dt +/-.1 sec   for    foreach
 Dictionary     6.8       7.3
 List           1.3       0.2
 Array          0.2       0.3

 Same test, different system:
 dt +/- .1 sec  for    foreach
 Dictionary     14.4   12.0
       List      1.7    0.1
      Array      0.5    0.7

随着对时序误差的更好估计(如何消除由于时间切片引起的系统测量误差?),可以对结果进行更多说明。

看起来 List/foreach 的访问速度最快,但开销正在扼杀它。

List/for 和 List/foreach 之间的区别是 stange。 也许涉及一些兑现?

此外,对于访问数组,使用for循环还是foreach循环都没有关系。 计时结果及其准确性使结果“具有可比性”。

到目前为止,使用字典是最慢的,我只考虑它是因为在左侧(索引器)我有一个稀疏的整数列表,而不是这个测试中使用的范围。

这是修改后的测试代码。

Dictionary<int, int> dict = new Dictionary<int, int>(6000000);
List<int> list = new List<int>(6000000);
Random rand = new Random(12345);
for (int i = 0; i < 6000000; i++)
{
    int n = rand.Next(5000);
    dict.Add(i, n);
    list.Add(n);
}
int[] arr = list.ToArray();

int chk = 0;
Stopwatch watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = dict.Count;
    for (int i = 0; i < len; i++)
    {
        chk += 1; // dict[i];
    }
}
watch.Stop();
long c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("         Dictionary(c)/for: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = dict.Count;
    for (int i = 0; i < len; i++)
    {
        chk += dict[i];
    }
}
watch.Stop();
long n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("         Dictionary(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = list.Count;
    for (int i = 0; i < len; i++)
    {
        chk += 1; // list[i];
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("               List(c)/for: {0}ms ({1})", c_dt, chk);

watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = list.Count;
    for (int i = 0; i < len; i++)
    {
        chk += list[i];
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("               List(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    for (int i = 0; i < arr.Length; i++)
    {
        chk += 1; // arr[i];
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("              Array(c)/for: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    for (int i = 0; i < arr.Length; i++)
    {
        chk += arr[i];
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in dict.Keys)
    {
        chk += 1; // dict[i]; ;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in dict.Keys)
    {
        chk += dict[i]; ;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in list)
    {
        chk += 1; // i;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("           List(c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in list)
    {
        chk += i;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("           List(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in arr)
    {
        chk += 1; // i;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("          Array(c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in arr)
    {
        chk += i;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

由于 List<> 内部使用数组,所以基本性能应该是一样的。 两个原因,为什么 List 可能会稍微慢一些:

  • 要在列表中查找元素,调用 List 的方法,该方法在底层数组中进行查找。 所以你需要一个额外的方法调用。 另一方面,编译器可能会认识到这一点并优化“不必要的”调用。
  • 如果编译器知道数组的大小,它可能会做一些特殊的优化,而对于未知长度的列表它是做不到的。 如果列表中只有几个元素,这可能会带来一些性能改进。

要检查它是否对您有任何影响,最好将发布的计时功能调整为您计划使用的大小列表,并查看您的特殊情况的结果如何。

我有两个澄清要添加到@Marc Gravell 答案中。

测试是在 x64 版本的 .NET 6 中完成的。

测试代码在最后。

数组和列表未以相同方式测试

要在相同条件下测试数组和列表,也应该修改“for”。

for (int i = 0; i < arr.Length; i++)

新版本 :

int len = arr.Length;
for (int i = 0; i < len; i++)

瓶颈列表/foreach:

可以修复 List(List/foreach 测试)的瓶颈。

将其更改为:

list.ForEach(x => chk += x);

在配备 Core i7-10510U 的 Windows 10 pro 21H1 x64 笔记本电脑上测试运行

List/for Count out: 1495ms (589725196)
List/for Count in: 1706ms (589725196)
Array/for Count out: 945ms (589725196)
Array/for Count in: 1072ms (589725196)
List/foreach: 2114ms (589725196)
List/foreach fixed: 1210ms (589725196)
Array/foreach: 1179ms (589725196)

结果解读

Array/for比原始测试更快。 (少 12%)

List/foreach fixedList/for快。

List/foreach fixed接近Array/foreach

我已经多次运行这个测试。 结果会发生变化,但数量级保持不变。

这个测试的这些结果表明,你确实必须对性能有很大的需求才能被迫使用 Array。

根据用于操作 List 的方法,性能可以除以 2。

这个测试是部分的。 没有随机访问、直接访问、写访问测试等。

我是不是弄错了某些部分,或者您有其他提高性能的想法吗?

测试代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
static class Program
{
    static void Main()
    {        List<int> list = new List<int>(6000000);
        Random rand = new Random(12345);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next(5000));
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for Count out: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < list.Count; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for Count in: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = arr.Length;
            for (int i = 0; i < len; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for Count out: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for Count in: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            list.ForEach(i => chk += i);
        }
        watch.Stop();
        Console.WriteLine("List/foreach fixed: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}
static long[] longs = new long[500000];
static long[] longs2 = {};
static List<long> listLongs = new List<long> { };
static void Main(string[] args)
{
    Console.CursorVisible = false;
    Stopwatch time = new Stopwatch();

    time.Start();
    for (int f = 50000000; f < 50255000; f++)
    {
        listLongs.Add(f);
    }

    //List  Time: 1ms    Count : 255000
    Console.WriteLine("List Time: " + time.ElapsedMilliseconds + " | Count: " + listLongs.Count());

    time.Restart();
    time.Start();
    for (long i = 1; i < 500000; i++)
    {
        longs[i] = i * 200;
    }

    //Array Time: 2ms Length: 500000 (Unrealistic Data)
    Console.WriteLine("Array Time: " + time.ElapsedMilliseconds + " | Length: " + longs.Length);

    time.Restart();
    time.Start();
    for (int i = 50000000; i < 50055000; i++)
    {
        longs2 = longs2.Append(i).ToArray();
    }

    //Array Time: 17950ms Length: 55000
    Console.WriteLine("Array Append Time: " + time.ElapsedMilliseconds + " | Length: " + longs2.Length);

    Console.ReadLine();
}
类型 时间
大批 2毫秒 500000
列表 1ms 255000
数组追加 17950ms 55000

如果您计划不断地将少量数据附加到数组中,那么 list 会更快

这实际上取决于您将如何使用该数组。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM