配列とリストのパフォーマンス


191

頻繁に繰り返す必要がある整数のリスト/配列が必要だとします。つまり、非常に頻繁です。理由はさまざまですが、大量処理の最も内側のループの中心にあると言います。

一般に、サイズが柔軟なため、リスト(リスト)の使用を選択します。その上、msdnのドキュメントでは、リストは内部で配列を使用しており、同じくらい高速に実行する必要があると主張しています(Reflectorで簡単に確認すると、これが確認されます)。それでも、多少のオーバーヘッドが伴います。

これを実際に測定した人はいますか?リストを6M回繰り返すと、配列と同じ時間がかかりますか?


3
パフォーマンスの問題はさておき、サイズが固定されているため、リストよりも配列を使用することをお勧めします(もちろん、アイテムの数を変更する必要がない場合)。既存のコードを読むとき、私はそれが参考にすばやくアイテムがされていることを知って見つける強制ではなく、さらにダウン機能のコードを検査するより、サイズが固定されています。
ウォーレンスティーブンス

2
T[]List<T>は、パフォーマンスに大きな違いをもたらす可能性があります。.NET 4.0でリストから配列に移動するために、非常に(ネストされた)ループを多用するアプリケーションを最適化しました。おそらく5%から10%の改善を期待していましたが、40%以上高速化しました!リストから配列に直接移動する以外の変更はありません。すべての列挙はforeachステートメントで行われました。Marc Gravellの回答によると、foreachwith List<T>は特に悪いようです。
2015

回答:


221

測定が非常に簡単...

長さが固定されていることがわかっている少数のタイトループ処理コードでは、マイクロ最適化の余分な小さなビットに配列を使用します。配列は可能わずかに速い場合は、フォームのために/インデクサを使用する-が、IIRCは、配列内のデータの種類によって異なります信じています。ただし、マイクロ最適化する必要がない限り、シンプルにして使用するList<T>などしてください。

もちろん、これはすべてのデータを読み取っている場合にのみ当てはまります。ディクショナリは、キーベースの検索の方が高速です。

これが「int」を使用した私の結果です(2番目の数字は、すべてが同じ作業を行ったことを確認するためのチェックサムです)。

(バグを修正するために編集)

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();
    }
}

8
興味深い詳細:リグのリリース/デバッグ(.net 3.5 sp1)の時間:0.92、0.80、0.96、0.93; これは、他のケースよりも約10%優れてArray / forループを最適化するために、VMにある程度のインテリジェンスがあることを示しています。
デビッドシュミット

2
はい、array / forにはJIT最適化があります。実際には、Lengthケースが含まれているという印象を受けていました(修正されていることがわかっているため)。更新していただきありがとうございます。
Marc Gravell

2
変だ。私の非常に類似したテストでは、配列を使用する場合のforとforeachの間に大きな違いはありません。他の人が実行できるベンチマーク付きのブログ投稿で徹底的に調査し、結果を送信します...
Jon Skeet

1
各テスト(chk1、chk2、chk3、chk4)のchkに異なる変数を使用すると、劇的に異なる結果が得られます。リスト/ for = 1303ms、アレイ/ for = 433ms。何かアイデアはありますか?
Jon

4
上記のジョンのコメントで言及されているジョンスキートのブログへのリンクが壊れていました。以下は更新されたリンクです。codeblog.jonskeet.uk/2009/01/29/…–
Josh DeLong

88

概要:

  • 配列を使用する必要があります:

    • できるだけ頻繁に。これは高速で、同じ量の情報に対して最小のRAM範囲を使用します。
    • 必要な細胞の正確な数がわかっている場合
    • 配列に保存されたデータが85000 b未満の場合(85000/32 =整数データの場合は2656要素)
    • 高いランダムアクセス速度が必要な場合
  • リストは使用する必要があります:

    • リストの最後にセルを追加する必要がある場合(多くの場合)
    • リストの最初/中央にセルを追加する必要がある場合(頻繁ではない)
    • 配列に保存されたデータが85000 b未満の場合(85000/32 =整数データの場合は2656要素)
    • 高いランダムアクセス速度が必要な場合
  • LinkedListを使用する必要があります:

    • リストの先頭/中央/末尾にセルを追加する必要がある場合(多くの場合)
    • 必要に応じて、順次アクセス(順方向/逆方向)のみ
    • 大きなアイテムを保存する必要があるが、アイテム数が少ない場合。
    • リンクに追加のメモリを使用するため、大量のアイテムには使用しないでください。

詳細:

色の意味

配列vsリストvsリンクリスト

はるかに詳細:

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


リストのプリペンドは比較的高速ですが、挿入は遅いというあなたの主張に少し混乱しています。挿入も線形時間であり、先頭に追加するよりも平均で50%高速です。
マイクマリナウスキ

1
c#リストの@MikeMarynowskiは、Arrayのラッパーです。したがって、リストへの挿入の場合は、ある時点までの線形時間しかありません。このシステムにより、新しい1つの大きなアレイが作成され、古いアレイからアイテムをコピーする時間が必要になります。
Andrew

先頭に同じです。
マイクマリナウスキ

プリペンド操作は0での挿入にすぎません。これはパフォーマンスの点で最悪の場合の挿入であるため、挿入が遅い場合、プリペンドはさらに遅くなります。
Mike Marynowski

insertとprependはどちらもO(n)(償却)です。プリペンドは挿入ですが、リスト内のすべての項目を1つ上に移動する必要があるため、可能な限り遅い挿入です。ランダムな場所への挿入では、挿入ポイントよりもインデックスが高いアイテムを上に移動するだけなので、平均で50%のアイテムになります。
マイクマリナウスキー

26

パフォーマンスはかなり似ていると思います。リストと配列を使用するときに伴うオーバーヘッドは、リストに項目を追加するときのIMHOと、配列の容量に達したときにリストが内部で使用する配列のサイズを増やす必要があるときです。

容量が10のリストがあるとします。11番目の要素を追加すると、リストは容量を増やします。リストの容量を保持するアイテムの数に初期化することで、パフォーマンスへの影響を減らすことができます。

しかし、リストの反復処理が配列の反復処理と同じくらい高速かどうかを判断するには、ベンチマークを実行してみませんか?

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クラスのインデクサーのゲッターは次のようになります。

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

ご覧のとおり、リストのインデクサーを使用すると、リストは、内部配列の境界から出ていないかどうかのチェックを実行します。この追加のチェックには費用がかかります。


こんにちはフレデリク、ありがとう!リストが配列の2倍の時間を費やしたことをどのように説明しますか。あなたが期待するものではありません。要素の数を増やしようとしましたか?
ボアズ

1
this._items [index];は返されません。インデックスが範囲外の場合、すでに例外をスローしますか?最終結果が同じかどうかにかかわらず、.NETでこの追加のチェックが行われるのはなぜですか?
John Mercier

@John Mercierチェックはリストのサイズ(現在含まれているアイテムの数)に対して行われます。これは、_items配列の容量とは異なり、おそらく_items配列の容量よりも小さいです。追加のたびに再割り当てする必要がないため、将来のアイテムをすばやく追加できるように、アレイには過剰な容量が割り当てられています。
Trasvi

21

(ループ内ではなく)どちらかから単一の値を取得している場合は、両方が境界チェックを実行します(マネージコードを使用していることに注意してください)。これが大したことではない理由については、後の注を参照してください。

独自のfor(int int i = 0; i <x。[Length / Count]; i ++)を使用している場合、主な違いは次のとおりです。

  • アレイ:
    • 境界チェックが削除されました
  • リスト
    • 境界チェックが実行されます

foreachを使用している場合、主な違いは次のとおりです。

  • アレイ:
    • 反復を管理するためのオブジェクトは割り当てられていません
    • 境界チェックが削除されました
  • リストと呼ばれる変数を介してリストします。
    • 反復管理変数はスタックに割り当てられます
    • 境界チェックが実行されます
  • IListとして知られている変数を介してリストします。
    • 反復管理変数はヒープに割り当てられます
    • 境界チェックも実行されます。リストの値はforeach中に変更できませんが、配列は変更できます。

境界チェックは大したことではありません(特に、深いパイプラインと分岐予測を備えたCPUを使用している場合-最近のほとんどの標準)が、それが問題であるかどうかを判断できるのは自分のプロファイリングだけです。ヒープ割り当てを回避しているコードの一部にいる場合(良い例はライブラリまたはハッシュコード実装です)、変数がIListではなくListとして型指定されていることを確認すると、その落とし穴が回避されます。いつものように、重要であればプロファイルを作成します。


11

[ この質問も参照してください ]

Marcの回答を実際の乱数を使用するように変更し、すべてのケースで実際に同じ処理を実行しました。

結果:

         foreach
アレイ:1575ms 1575ms(+ 0%)
リスト:1630ms 2627ms(+ 61%)
         (+ 3%)(+ 67%)

(チェックサム:-1000038876)

VS 2008 SP1でリリースとしてコンパイル。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();
    }
}

それは奇妙です-/ o + / debug-を使用してコマンドライン(3.5SP1)からビルドした正確なコードを実行しました。結果は次のとおりです。list / for:1524; 配列/用:1472; list / foreach:4128; array / foreach:1484。
Jon Skeet、

これはリリースとしてコンパイルされたと言っています-デバッグするのではなく実行したことを確認できますか?ばかげた質問ですが、私は結果を説明することはできません...
ジョンスキート

2

測定は優れていますが、内部ループで正確に何を実行しているかによって、大幅に異なる結果が得られます。自分の状況を測定します。マルチスレッドを使用している場合、それだけでは重要なアクティビティです。


2

実際、ループ内でいくつかの複雑な計算を実行する場合、配列インデクサーとリストインデクサーのパフォーマンスはごくわずかであるため、最終的には問題になりません。


2

辞書、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();
    }
}

2

要素の数を増やして容量を追加しようとしないでください。

パフォーマンス

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

配列のサイズ変更を60k回行うと遅くなります...確かに実際の使用法では、必要な追加スロットの数を確認し、長さ+ 60kにサイズ変更してから、挿入を圧縮します。
tobriand

より多くのスペースが必要になるたびにサイズを2倍にすると、配列のサイズ変更は非常に高速になります。最初の宣言後に一度だけサイズを変更するのと同じくらいの時間がかかるようです。これにより、リストの柔軟性と配列のほとんどの速度が得られます。
user1318499 2018年

2

他の回答に投稿されたベンチマークが、コンパイラーがループを最適化、削除、またはマージする余地を残してしまうのではないかと心配していたため、次のように記述しました。

  • 使用された予測不可能な入力(ランダム)
  • 計算された結果をコンソールに出力して実行します
  • 繰り返しごとに入力データを変更します

直接配列のパフォーマンスは、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;
}

0

List <>は内部的に配列を使用するため、基本的なパフォーマンスは同じです。リストが少し遅くなる2つの理由:

  • リスト内の要素を検索するために、基になる配列を検索するListのメソッドが呼び出されます。そこで、追加のメソッド呼び出しが必要です。一方、コンパイラはこれを認識し、「不要な」呼び出しを最適化する可能性があります。
  • 配列のサイズがわかっている場合、コンパイラーは特別な最適化を行う場合があります。これは、長さが不明なリストでは実行できません。リストにいくつかの要素しかない場合、これによりパフォーマンスが向上する可能性があります。

違いがあるかどうかを確認するには、投稿するタイミング関数を、使用する予定のサイズのリストに合わせて調整し、特殊なケースの結果を確認することをお勧めします。


0

同様の質問があったので、これは私に速いスタートを切った。

私の質問はもう少し具体的です、「再帰的なアレイ実装のための最速の方法は何ですか」

Marc Gravellが行ったテストでは、アクセスタイミングが正確に示されているわけではありません。彼のタイミングには、配列とリストのループも含まれます。また、テストしたい3番目の方法である「辞書」も思いついたので、比較のためにhistテストコードを拡張しました。

まず、定数を使用してテストを行います。これにより、ループを含む特定のタイミングが得られます。これは、実際のアクセスを除いた「ベア」タイミングです。次に、対象の構造にアクセスしてテストを行います。これにより、「オーバーヘッドを含む」タイミング、ループ、および実際のアクセスが得られます。

「ベア」タイミングと「オーバーヘッドインダッド」タイミングの違いから、「ストラクチャアクセス」タイミングがわかります。

しかし、このタイミングはどのくらい正確ですか?テストウィンドウでは、shureのスライスが行われます。時間スライスについての情報はありませんが、テスト中に数十ミリ秒のオーダーで均等に分布していると想定しています。つまり、タイミングの精度は+/- 100ミリ秒程度になるはずです。少し大まかな見積もりですか?とにかく、体系的な測量エラーの原因。

また、テストは最適化なしの「デバッグ」モードで行われました。そうしないと、コンパイラが実際のテストコードを変更する可能性があります。

したがって、「(c)」とマークされた定数と「(n)」とマークされたアクセスの2つの結果が得られ、「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の違いはスタンジです。たぶんいくつかの現金化が含まれていますか?

さらに、配列にアクセスする場合、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);

0

いくつかの簡単なテストで、私は合理的に集中的な数学と呼ばれるものにおいて、2つの組み合わせがより優れていることを発見しました:

タイプ: 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言語になる可能性があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.