4

ジェネリック リストの算術演算を最適化しようとしています。以下に定義されているように、nullable double の 3 つのリストがあります。

List<double?> list1 = new List<double?>();
List<double?> list2 = new List<double?>();
List<double?> listResult = new List<double?>();

int recordCount = list1.Count > list2.Count ? list2.Count : list1.Count;

for (int index = 0; index < recordCount; index++)
{
      double? result = list1[index] + list2[index];
      listResult.Add(result);
}

巨大なリストがある場合、この操作をより速く実行する方法はありますか?

ご意見ありがとうございます。

4

5 に答える 5

9

巨大なリストがある場合、この操作をより速く実行する方法はありますか?

集計後まで、結果のリスト作成を移動できます。

List<double?> list1 = new List<double?>();
List<double?> list2 = new List<double?>();

int recordCount = list1.Count > list2.Count ? list2.Count : list1.Count;
List<double?> listResult = new List<double?>(recordCount);

これにより、結果に必要な正確な容量を指定し、リスト自体内での再割り当てを回避できます。「巨大なリスト」の場合、これはおそらく最も遅い部分の 1 つです。これは、リストが大きくなるにつれてメモリ割り当てとコピーがここで最も遅い操作になるためです。

また、計算が単純な場合は、複数のコアを使用できる可能性があります。

List<double?> list1 = new List<double?>();
List<double?> list2 = new List<double?>();

int recordCount = list1.Count > list2.Count ? list2.Count : list1.Count;

var results = new double?[recordCount]; // Use an array here

Parallel.For(0, recordCount, index => 
    {
        double? result = list1[index] + list2[index];
        results[index] = result;
    });

ここでの「作業」は非常に単純であるため、並列処理を最大限に活用するには、おそらく実際にはカスタム パーティショナーが必要になるでしょう (詳細については、「方法: 小さなループ本体を高速化する」を参照してください)。

var results = new double?[recordCount]; // Use an array here
var rangePartitioner = Partitioner.Create(0, recordCount);

Parallel.ForEach(rangePartitioner, range => 
    {
        for (int index = range.Item1; index < range.Item2; index++)
        {
            results[index] = list1[index] + list2[index];
        }
    });

ただし、これがボトルネックでない場合は、LINQ を使用してワンライナーでこれを行うことができます。

var results = list1.Zip(list2, (one, two) => one + two).ToList();

ただし、パフォーマンスが実際にボトルネックである場合、これはループを自分で処理するよりも (非常にわずかに) 効率が低下します。

于 2012-10-05T01:20:15.437 に答える
0

リストの代わりに生の配列を使用する機能がある場合は、これを確実に高速化できます。これは、どの程度低レベルにするかによって異なります。ソースのいくつかのバグを修正して、3つの異なるバージョンを作成しました。結果の新しいリストを作成することで、コードと同じようになります(私は、容量を使用するコンストラクターを自由に使用して、多数の中間割り当てを防止しました)。

また、リストを削除すると効率が上がるという観点から、2つの配列を3つに合計するバージョンも作成しました。

最後に、安全でないコードを使用して、ポインターを使用して最初の配列を2番目の配列に追加するバージョンを作成しました。

比較の結果は以下のとおりです。

Timings: 500000 iterations of 10000-item lists
  Lists:           00:00:13.9184166
  Arrays (safe):   00:00:08.4868231
  Arrays (unsafe): 00:00:03.0901603

Press any key to continue...

私が使用したコードは、このGithubの要点にあります。

安全でないコードは少し最適化が多すぎるかもしれませんが、これら3つのサンプルの違いを見るのはかなり印象的です。わかりやすくするために、安全なコードを使用し、配列を使用することをお勧めします。

于 2012-10-05T01:49:43.403 に答える
0

リストのサイズが事前にわかっている場合は、単純な配列の方が高速に実行されるはずです。このように作成されました:

double?[] Array1 = new double?[10];
于 2012-10-05T01:19:29.300 に答える
0

最初にできることは、結果リストの容量を指定することです

List<double?> listResult = new List<double?>(recordCount);

これにより、結果のスペースが事前に割り当てられ、List.Add() 呼び出しのそれぞれで時間を節約できます。

パフォーマンスが本当に心配な場合は、リストをチャンクに分割し、複数のスレッドを起動して部分的な結果セットを実行し、完了したら完全なセットをマージして戻すことができます。

于 2012-10-05T01:22:32.707 に答える
0
var result = from i in
            Enumerable.Range(0, Math.Min(list1.Count, list2.Count))
            select list1.ElementAtOrDefault(i) + list2.ElementAtOrDefault(i);
foreach (var item in result)
{
Debug.Write(item.Value);
}
于 2012-10-05T01:24:19.993 に答える