web-dev-qa-db-ja.com

C#List <T> .ToArrayのパフォーマンスが悪いですか?

.Net 3.5(C#)を使用していますが、C#のパフォーマンスを聞いたことがありますList<T>.ToArrayは「悪い」です。これは、すべての要素のメモリがコピーされて新しい配列が形成されるためです。本当?

24
George2

いいえ、それは真実ではありません。すべての要素(*)をメモリコピーして新しい配列を形成するだけなので、パフォーマンスは良好です。

もちろん、それはあなたが「良い」または「悪い」パフォーマンスとして定義するものに依存します。

(*)参照型の参照、値型の値。

[〜#〜]編集[〜#〜]

あなたのコメントに応えて、Reflectorを使用することは実装をチェックする良い方法です(以下を参照)。または、それをどのように実装するかについて数分間考えて、Microsoftのエンジニアがもっと悪い解決策を思い付かないことを信頼してください。

public T[] ToArray()
{
    T[] destinationArray = new T[this._size];
    Array.Copy(this._items, 0, destinationArray, 0, this._size);
    return destinationArray;
}

もちろん、「良い」または「悪い」パフォーマンスは、いくつかの代替案に関連する意味しかありません。特定のケースで、測定可能なほど高速な目標を達成するための代替手法がある場合は、パフォーマンスが「悪い」と見なすことができます。そのような代替手段がない場合、パフォーマンスは「良好」(または「十分に良好」)です。

編集2

コメントへの応答:「オブジェクトの再構築はありませんか?」 :

参照型の再構築はありません。値型の場合、値はコピーされます。これは、大まかに再構成として説明できます。

50
Joe

ToArray()を呼び出す理由

  • 戻り値が変更されることを意図していない場合は、配列として返すと、その事実が少し明確になります。
  • 呼び出し元がデータに対して多くの非順次アクセスを実行することが予想される場合、List <>よりも配列のパフォーマンスが向上する可能性があります。
  • 配列を期待するサードパーティの関数に戻り値を渡す必要があることがわかっている場合。
  • .NETバージョン1または1.1で動作する必要がある呼び出し関数との互換性。これらのバージョンには、List <>タイプ(または、さらに言えば、ジェネリックタイプ)はありません。

ToArray()を呼び出さない理由

  • 呼び出し元が要素を追加または削除する必要がある場合は、List <>が絶対に必要です。
  • 特に呼び出し元が順次データにアクセスしている場合、パフォーマンス上の利点は必ずしも保証されません。 List <>から配列に変換する追加の手順もあり、処理に時間がかかります。
  • 呼び出し元はいつでもリストを配列に変換できます。

ここ から取得

19
Sorantis

はい、それはすべての要素のメモリコピーを行うのは事実です。パフォーマンスの問題ですか?それはあなたのパフォーマンス要件に依存します。

Listには、すべての要素を保持するための配列が内部に含まれています。容量がリストに対して十分でなくなると、配列は大きくなります。その場合はいつでも、リストはすべての要素を新しい配列にコピーします。これは常に発生し、ほとんどの人にとってパフォーマンスの問題はありません。

例えば。デフォルトのコンストラクターを持つリストは容量16から始まり、17番目の要素を.Add()すると、サイズ32の新しい配列が作成され、16個の古い値がコピーされ、17番目が追加されます。

サイズの違いは、ToArray()がプライベート参照を渡す代わりに新しい配列インスタンスを返す理由でもあります。

5
chris166

配列に新しい参照を作成しますが、そのメソッドで実行できることと実行する必要があるのはそれだけです...

1
TimM

パフォーマンスは相対的な用語で理解する必要があります。配列をリストに変換するには、配列をコピーする必要があり、そのコストは配列のサイズによって異なります。しかし、そのコストをプログラムが実行している他のことと比較する必要があります。そもそも、アレイに入れる情報をどのようにして入手したのですか?ディスク、ネットワーク接続、またはデータベースからの読み取りによるものである場合、メモリ内のアレイコピーが、所要時間に検出可能な違いをもたらす可能性はほとんどありません。

1

これは、Microsoftの 公式ドキュメント がList.ToArrayの時間計算量について述べていることです。

要素は、Array.Copyを使用してコピーされます。これはO(n)操作です。ここで、nはCountです。

次に、 Array.Copyを見て 、通常はデータのクローンを作成するのではなく、参照を使用していることがわかります。

SourceArrayとdestinationArrayが両方とも参照型の配列であるか、両方ともObject型の配列である場合、シャローコピーが実行されます。配列の浅いコピーは、元の配列と同じ要素への参照を含む新しい配列です。要素自体または要素によって参照されるものはコピーされません。対照的に、配列のディープコピーは、要素と、要素によって直接または間接的に参照されるすべてのものをコピーします。

したがって、結論として、これはリストから配列を取得する非常に効率的な方法です。

1
J Gaspar

長さがわかっているあらゆる種類のList/ICollectionの場合、最初から正確に正しいサイズの配列を割り当てることができます。

T[] destinationArray = new T[this._size];
Array.Copy(this._items, 0, destinationArray, 0, this._size);
return destinationArray;

ソースタイプがIEnumerable(リスト/コレクションではない)の場合、ソースは次のとおりです。

items = new TElement[4];
..
if (no more space) {
    TElement[] newItems = new TElement[checked(count * 2)];
    Array.Copy(items, 0, newItems, 0, count);
    items = newItems;

サイズ4から始まり、指数関数的に増加し、スペースがなくなるたびに2倍になります。倍増するたびに、メモリを再割り当てしてデータをコピーする必要があります。

ソースデータのサイズがわかっていれば、このわずかなオーバーヘッドを回避できます。ただし、ほとんどの場合、たとえば配列サイズ<= 1024の場合、実行速度が非常に速いため、この実装の詳細について考える必要はありません。

参照:Enumerable.cs、List.cs(F12ing into them)、Joeの回答

0
Curtis Yallop