_List<T>
_ vs _LinkedList<T>
_ についての記事を読んだので、自分でいくつかの構造をベンチマークすることにしました。 _Stack<T>
_、_Queue<T>
_、_List<T>
_、_LinkedList<T>
_のベンチマークは、データを追加したり、フロント/エンドからデータを削除したりして行いました。ベンチマーク結果は次のとおりです。
_ Pushing to Stack... Time used: 7067 ticks
Poping from Stack... Time used: 2508 ticks
Enqueue to Queue... Time used: 7509 ticks
Dequeue from Queue... Time used: 2973 ticks
Insert to List at the front... Time used: 5211897 ticks
RemoveAt from List at the front... Time used: 5198380 ticks
Add to List at the end... Time used: 5691 ticks
RemoveAt from List at the end... Time used: 3484 ticks
AddFirst to LinkedList... Time used: 14057 ticks
RemoveFirst from LinkedList... Time used: 5132 ticks
AddLast to LinkedList... Time used: 9294 ticks
RemoveLast from LinkedList... Time used: 4414 ticks
_
コード:
_using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace Benchmarking
{
static class Collections
{
public static void run()
{
Random Rand = new Random();
Stopwatch sw = new Stopwatch();
Stack<int> stack = new Stack<int>();
Queue<int> queue = new Queue<int>();
List<int> list1 = new List<int>();
List<int> list2 = new List<int>();
LinkedList<int> linkedlist1 = new LinkedList<int>();
LinkedList<int> linkedlist2 = new LinkedList<int>();
int dummy;
sw.Reset();
Console.Write("{0,40}", "Pushing to Stack...");
sw.Start();
for (int i = 0; i < 100000; i++)
{
stack.Push(Rand.Next());
}
sw.Stop();
Console.WriteLine(" Time used: {0,9} ticks", sw.ElapsedTicks);
sw.Reset();
Console.Write("{0,40}", "Poping from Stack...");
sw.Start();
for (int i = 0; i < 100000; i++)
{
dummy = stack.Pop();
dummy++;
}
sw.Stop();
Console.WriteLine(" Time used: {0,9} ticks\n", sw.ElapsedTicks);
sw.Reset();
Console.Write("{0,40}", "Enqueue to Queue...");
sw.Start();
for (int i = 0; i < 100000; i++)
{
queue.Enqueue(Rand.Next());
}
sw.Stop();
Console.WriteLine(" Time used: {0,9} ticks", sw.ElapsedTicks);
sw.Reset();
Console.Write("{0,40}", "Dequeue from Queue...");
sw.Start();
for (int i = 0; i < 100000; i++)
{
dummy = queue.Dequeue();
dummy++;
}
sw.Stop();
Console.WriteLine(" Time used: {0,9} ticks\n", sw.ElapsedTicks);
sw.Reset();
Console.Write("{0,40}", "Insert to List at the front...");
sw.Start();
for (int i = 0; i < 100000; i++)
{
list1.Insert(0, Rand.Next());
}
sw.Stop();
Console.WriteLine(" Time used: {0,9} ticks", sw.ElapsedTicks);
sw.Reset();
Console.Write("{0,40}", "RemoveAt from List at the front...");
sw.Start();
for (int i = 0; i < 100000; i++)
{
dummy = list1[0];
list1.RemoveAt(0);
dummy++;
}
sw.Stop();
Console.WriteLine(" Time used: {0,9} ticks\n", sw.ElapsedTicks);
sw.Reset();
Console.Write("{0,40}", "Add to List at the end...");
sw.Start();
for (int i = 0; i < 100000; i++)
{
list2.Add(Rand.Next());
}
sw.Stop();
Console.WriteLine(" Time used: {0,9} ticks", sw.ElapsedTicks);
sw.Reset();
Console.Write("{0,40}", "RemoveAt from List at the end...");
sw.Start();
for (int i = 0; i < 100000; i++)
{
dummy = list2[list2.Count - 1];
list2.RemoveAt(list2.Count - 1);
dummy++;
}
sw.Stop();
Console.WriteLine(" Time used: {0,9} ticks\n", sw.ElapsedTicks);
sw.Reset();
Console.Write("{0,40}", "AddFirst to LinkedList...");
sw.Start();
for (int i = 0; i < 100000; i++)
{
linkedlist1.AddFirst(Rand.Next());
}
sw.Stop();
Console.WriteLine(" Time used: {0,9} ticks", sw.ElapsedTicks);
sw.Reset();
Console.Write("{0,40}", "RemoveFirst from LinkedList...");
sw.Start();
for (int i = 0; i < 100000; i++)
{
dummy = linkedlist1.First.Value;
linkedlist1.RemoveFirst();
dummy++;
}
sw.Stop();
Console.WriteLine(" Time used: {0,9} ticks\n", sw.ElapsedTicks);
sw.Reset();
Console.Write("{0,40}", "AddLast to LinkedList...");
sw.Start();
for (int i = 0; i < 100000; i++)
{
linkedlist2.AddLast(Rand.Next());
}
sw.Stop();
Console.WriteLine(" Time used: {0,9} ticks", sw.ElapsedTicks);
sw.Reset();
Console.Write("{0,40}", "RemoveLast from LinkedList...");
sw.Start();
for (int i = 0; i < 100000; i++)
{
dummy = linkedlist2.Last.Value;
linkedlist2.RemoveLast();
dummy++;
}
sw.Stop();
Console.WriteLine(" Time used: {0,9} ticks\n", sw.ElapsedTicks);
}
}
}
_
違いはso劇的です!
ご覧のとおり、_Stack<T>
_と_Queue<T>
_のパフォーマンスは高速で匹敵します。
_List<T>
_の場合、フロントとエンドの使用には大きな違いがあります!そして驚いたことに、最後から追加/削除するパフォーマンスは、実際には_Stack<T>
_のパフォーマンスに匹敵します。
_LinkedList<T>
_の場合、フロントでの操作は高速です(_List<T>
_よりも-er)、しかし、最後には、削除するのに非常に遅い 最後で操作することもです。
だから...専門家は次のことを説明できます:
Stack<T>
_の使用と_List<T>
_の使用のパフォーマンスの類似性、List<T>
_の先頭と末尾の使用の違い、およびLinkedList<T>
_の終わりの使用がso遅い理由Last()
の使用によるコーディングエラーであるため適用されません。CodesInChaosのおかげです)?_List<T>
_がフロントをそれほどうまく処理しない理由を知っていると思います... _List<T>
__はリスト全体を前後に移動する必要があるためです。間違っている場合は修正してください。
追伸私の_System.Diagnostics.Stopwatch.Frequency
_は_2435947
_であり、プログラムはWindows 7 Visual Studio 2010で.NET 4クライアントプロファイルを対象とし、C#4.0でコンパイルされています。
1:について
_Stack<T>
_のパフォーマンスと_List<T>
_のパフォーマンスが似ていることは驚くことではありません。私は両方とも倍増戦略で配列を使用することを期待しています。これは、償却された一定時間の追加につながります。
_List<T>
_を使用できるすべての場所で_Stack<T>
_を使用できますが、 表現力の少ないコードになります 。
2:について
_
List<T>
_がフロントをそれほどうまく処理しない理由を知っていると思います... _List<T>
_はリスト全体を前後に移動する必要があるためです。
そのとおりです。最初に要素を挿入/削除すると、すべての要素が移動するため、費用がかかります。一方、最初に要素を取得または置換するのは安価です。
3:について
遅い_LinkedList<T>.RemoveLast
_値は、ベンチマークコードの誤りです。
二重リンクリストの最後のアイテムを削除または取得するのは安価です。 _LinkedList<T>
_の場合、RemoveLast
とLast
が安価であることを意味します。
ただし、Last
プロパティではなく、LINQの拡張メソッドLast()
を使用していました。 _IList<T>
_を実装していないコレクションでは、リスト全体を反復処理し、O(n)
ランタイムを提供します。
List<T>
は 動的な過剰割り当て配列 (他の多くの言語の標準ライブラリでも見られるデータ構造)です。これは、リストのサイズよりも大きいことが多い「静的」配列(サイズ変更できない配列、.NETでは単に「配列」と呼ばれる)を内部的に使用することを意味します。その後、追加するだけでカウンターをインクリメントし、内部アレイの以前に使用されていない次のスロットを使用します。内部配列がすべてのアイテムを収容するために小さくなった場合にのみ、配列は再割り当てされます(すべての要素をコピーする必要があります)。それが起こると、配列のサイズは通常2倍(定数ではなく)増加します。
これにより、amortized時間の複雑さ(基本的に、長い操作シーケンスでの操作ごとの平均時間)がO(1)最悪の場合でも。先頭に追加する場合、このような最適化は実行できません(少なくともランダムアクセスとO(1)末尾に追加する間は) 。常にすべての要素をコピーして新しいスロットに移動する必要があります(最初のスロットに追加された要素用のスペースを作ります)Stack<T>
同じことをします 、あなたはただ片方の端(高速端)でしか操作しないため、前面への追加との不一致に注意してください。
リンクされたリストの最後を取得することは、リストの内部に大きく依存します。 1つcanは最後の要素への参照を維持しますが、これによりリスト上のすべての操作がより複雑になり、可能性があります(例はありません)手)いくつかの操作をより高価にします。このような参照が不足しているため、最後に追加するには、リンクされたリストのall要素を最後まで検索する必要があります。もちろん、リストでは非常に遅くなります自明でないサイズ。
@CodesInChaosが指摘したように、リンクリストの操作に欠陥がありました。現在表示されている最後の高速検索は、上記のように、最後のノードへの参照を明示的に保持しているLinkedList<T>
が原因である可能性が高いです。どちらかの端にない要素を取得することは依然として遅いことに注意してください。
速度は基本的に、アイテムの挿入、削除、または検索に必要な操作の数に由来します。既にお気付きのように、そのリストにはメモリ転送が必要です。
スタックはリストであり、最上位の要素でのみアクセスできます。コンピューターは常にそれがどこにあるかを知っています。
リンクリストは別のことです。リストの開始点は既知であるため、開始点の追加または削除は非常に高速ですが、最後の要素の検索には時間がかかります。最後の要素OTOHの場所をキャッシュすることは、追加する価値があるだけです。削除の場合、最後の要素への「フック」またはポインタを見つけるために、完全なリストから要素を1つ引いたものを走査する必要があります。
数字を見るだけで、各データ構造の内部構造について知識に基づいた推測を行うことができます。
stackの使用とListの最後のパフォーマンスの類似性、
Delnanで説明したように、どちらも内部で単純な配列を使用しているため、最後に作業するときは非常によく似た動作をします。スタックは、最後のオブジェクトにアクセスするだけのリストです。
リストの先頭と末尾の使用の違い
すでに正しく疑っています。リストの先頭を操作すると、基になる配列を変更する必要があります。通常、アイテムを追加すると、他のすべての要素を1つずつシフトする必要があります。削除と同じです。リストの両端を操作することがわかっている場合は、リンクリストを使用することをお勧めします。
linkedListの最後の使用が非常に遅い理由は?
通常、任意の位置でのリンクリストの要素の挿入と削除は、最大2つのポインターを変更するだけでよいため、一定の時間で実行できます。問題はその位置に到達することです。通常のリンクリストには、最初の要素へのポインタのみがあります。したがって、最後の要素に到達したい場合は、すべての要素を反復処理する必要があります。リンクリストで実装されたキューは、通常、最後の要素への追加のポインターを持つことでこの問題を解決します。したがって、要素を追加することも一定時間で可能です。より洗練されたデータ構造は、最初のおよびlast要素への両方のポインターを持ち、各要素が次のおよびprevious要素へのポインターも含む二重リンクリストです。
これについて学ぶべきことは、単一の目的のために作られたさまざまなデータ構造があり、非常に効率的に処理できることです。正しい構造を選択することは、何をしたいかに大きく依存します。
私はJavaバックグラウンドを持っています。あなたの質問は特定の言語よりも一般的なデータ構造に関係していると思います。また、ステートメントが間違っている場合は謝罪します。
1。 Stackの使用とListの最後のパフォーマンスの類似性
2。リストの先頭と末尾の使用の違い、および
少なくともJavaでは、スタックはarraysを使用して実装されます(C#に当てはまらない場合はおAびします。実装のソースを参照できます)。リストの場合も同じです。通常、配列の場合、最初の挿入に対応するために配列内の既存の値を下に移動する必要があるため、最後の挿入は最初よりも時間がかかりません。
Stack.Javaソースへのリンク およびそのスーパークラス ベクター
3。 LinkedListの最後の使用が非常に遅い理由
LinkedListはランダムアクセスを許可せず、挿入ポイントに到達する前にノードを通過する必要があります。最後のノードのパフォーマンスが遅いことがわかった場合、LinkedListの実装はsingly-linked listである必要があります。最後に要素にアクセスしながらパフォーマンスを最適化するために、二重にリンクされたリストを検討する必要があると思います。
以前のコードのいくつかの欠陥、特にランダムおよびダミー計算の影響を改善しました。配列は依然としてすべてを上回っていますが、Listのパフォーマンスは印象的であり、LinkedListはランダム挿入に非常に適しています。
ソートされた結果は次のとおりです。
12 array[i]
40 list2[i]
62 FillArray
68 list2.RemoveAt
78 stack.Pop
126 list2.Add
127 queue.Dequeue
159 stack.Push
161 foreach_linkedlist1
191 queue.Enqueue
218 linkedlist1.RemoveFirst
219 linkedlist2.RemoveLast
2470 linkedlist2.AddLast
2940 linkedlist1.AddFirst
コードは次のとおりです。
using System;
using System.Collections.Generic;
using System.Diagnostics;
//
namespace Benchmarking {
//
static class Collections {
//
public static void Main() {
const int limit = 9000000;
Stopwatch sw = new Stopwatch();
Stack<int> stack = new Stack<int>();
Queue<int> queue = new Queue<int>();
List<int> list1 = new List<int>();
List<int> list2 = new List<int>();
LinkedList<int> linkedlist1 = new LinkedList<int>();
LinkedList<int> linkedlist2 = new LinkedList<int>();
int dummy;
sw.Reset();
Console.Write( "{0,40} ", "stack.Push");
sw.Start();
for ( int i = 0; i < limit; i++ ) {
stack.Push( i );
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
sw.Reset();
Console.Write( "{0,40} ", "stack.Pop" );
sw.Start();
for ( int i = 0; i < limit; i++ ) {
stack.Pop();
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
sw.Reset();
Console.Write( "{0,40} ", "queue.Enqueue" );
sw.Start();
for ( int i = 0; i < limit; i++ ) {
queue.Enqueue( i );
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
sw.Reset();
Console.Write( "{0,40} ", "queue.Dequeue" );
sw.Start();
for ( int i = 0; i < limit; i++ ) {
queue.Dequeue();
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
//sw.Reset();
//Console.Write( "{0,40} ", "Insert to List at the front..." );
//sw.Start();
//for ( int i = 0; i < limit; i++ ) {
// list1.Insert( 0, i );
//}
//sw.Stop();
//Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
//
//sw.Reset();
//Console.Write( "{0,40} ", "RemoveAt from List at the front..." );
//sw.Start();
//for ( int i = 0; i < limit; i++ ) {
// dummy = list1[ 0 ];
// list1.RemoveAt( 0 );
// dummy++;
//}
//sw.Stop();
//Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
sw.Reset();
Console.Write( "{0,40} ", "list2.Add" );
sw.Start();
for ( int i = 0; i < limit; i++ ) {
list2.Add( i );
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
sw.Reset();
Console.Write( "{0,40} ", "list2.RemoveAt" );
sw.Start();
for ( int i = 0; i < limit; i++ ) {
list2.RemoveAt( list2.Count - 1 );
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
sw.Reset();
Console.Write( "{0,40} ", "linkedlist1.AddFirst" );
sw.Start();
for ( int i = 0; i < limit; i++ ) {
linkedlist1.AddFirst( i );
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
sw.Reset();
Console.Write( "{0,40} ", "linkedlist1.RemoveFirst" );
sw.Start();
for ( int i = 0; i < limit; i++ ) {
linkedlist1.RemoveFirst();
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
sw.Reset();
Console.Write( "{0,40} ", "linkedlist2.AddLast" );
sw.Start();
for ( int i = 0; i < limit; i++ ) {
linkedlist2.AddLast( i );
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
sw.Reset();
Console.Write( "{0,40} ", "linkedlist2.RemoveLast" );
sw.Start();
for ( int i = 0; i < limit; i++ ) {
linkedlist2.RemoveLast();
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
// Fill again
for ( int i = 0; i < limit; i++ ) {
list2.Add( i );
}
sw.Reset();
Console.Write( "{0,40} ", "list2[i]" );
sw.Start();
for ( int i = 0; i < limit; i++ ) {
dummy = list2[ i ];
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
// Fill array
sw.Reset();
Console.Write( "{0,40} ", "FillArray" );
sw.Start();
var array = new int[ limit ];
for ( int i = 0; i < limit; i++ ) {
array[ i ] = i;
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
sw.Reset();
Console.Write( "{0,40} ", "array[i]" );
sw.Start();
for ( int i = 0; i < limit; i++ ) {
dummy = array[ i ];
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
// Fill again
for ( int i = 0; i < limit; i++ ) {
linkedlist1.AddFirst( i );
}
sw.Reset();
Console.Write( "{0,40} ", "foreach_linkedlist1" );
sw.Start();
foreach ( var item in linkedlist1 ) {
dummy = item;
}
sw.Stop();
Console.WriteLine( sw.ElapsedMilliseconds.ToString() );
//
Console.WriteLine( "Press Enter to end." );
Console.ReadLine();
}
}
}