簡體   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