私はこのブログ投稿を読んでいました: http://kjellkod.wordpress.com/2012/02/25/why-you-should-never-ever-ever-use-linked-list-in-your-code -again /
そして実行するコードが見つかりました: http://ideone.com/62Emz
T5450 CPUを搭載した古いラップトップで、それぞれ32 KバイトのL1キャッシュと2メガバイトの(共通?)L2キャッシュを備えた古いラップトップで、gcc 4.7.2を使用してg++ -std=c++11
を使用してコンパイルし、次の結果を得ました。
********** Times in microseconds**********
Elements ADD (List, Vector) ERASE(List, Vector)
100, , 0, 0, 0, 0
200, , 0, 0, 15625, 0
500, , 0, 0, 0, 0
1000, , 15625, 0, 0, 15625
4000, , 109375, 140625, 46875, 31250
10000, , 750000, 875000, 312500, 187500
20000, , 2968750, 3468750, 1296875, 781250
40000, , 12000000, 13843750, 5359375, 3156250
Exiting test,. the whole measuring took 45375 milliseconds (45seconds or 0 minut
es)
少なくともADD
操作については、実際には反対のことを言っています。そのブログ投稿の作成者が言っていることと比較すると、リストはベクターよりもADD
の方が高速です。私の結果からどのような結論を出す必要がありますか?それは何かを証明しますか?何を考えたり理解したりする必要がありますか?
まず、効率についてのアドバイスを信じるのではなく、正しいことと測定を行うことをお祝いします!現代のコンピューターアーキテクチャでは、データ階層の小さな変更がランタイムにどの程度正確に影響するかを予測するのはこれまで以上に困難です。これは、メモリ階層の多くのレベル、順序外の実行、積極的なコードオプティマイザーなどが原因です。確かにあなたが測定したものであり、そうであれば、あなたはリストでより良くなるでしょう。
とはいえ、そのプログラムはあまり機能しません。実際には、ほとんど確実に、追加または削除するよりも頻繁に要素にアクセスします。ランダムアクセスが多いベンチマークを再実行すると、結果が好転する可能性があると思いますが、先ほど言ったことを覚えていますか? なし想定します。 常にメジャー。幸せなプロファイリング!
その記事のタイトルは意図的に不誠実です。著者は3つのポイントを作成しています:リンクリストのトラバースと検索が遅い、つまり、ほとんどの人は気づかないどのくらいキャッシュミスによる線形配列検索よりも遅いおそらく真実であり、リンクされたリストを選択するとき、ほとんどの人は検索時間を考慮に入れていません。これはおそらく偽です。
彼のベンチマークプログラムには、不快な部分である追加と削除に加えて検索時間が含まれています。リンクされたリストを選択するとき、私が最初に自分に尋ねるのは、検索時間の短さを回避できるのか、それとも許容できるトレードオフなのかです。それはほとんどの人に当てはまると思います。
たとえば、リンクリストを最後に使用したのは、いくつかのアイテムを最後にアクセスした最近の日付で並べ替えておく必要があるときでした。最も一般的な操作はるかには、リストの中央から前面にアイテムを移動することでしたO(1)リンクされたリストの操作です。しかし、リンクされたリストノードへのポインタをとにかく必要な別のデータ構造に格納すると便利であることが判明したため、検索もO(1)になります。
ただし、他の状況では、O(n)検索を保持しました。これは、リンクリストのセマンティクスがコードを簡略化し、パフォーマンスヒットが無視できるためです。独自のテストでは、測定された差が示されています100分の1秒で4000ノードを追加または削除できますほとんどのアプリケーションではまったく気付かれません。
著者とは異なる結果を彼自身のベンチマークで得たという事実も非常に興味深いものであり、独自の測定を行うべき理由をよく示しています。コンパイラ、オペレーティングシステム、標準ライブラリの実装、さらにはシステムで実行されている他のプロセスでさえ、コードが生成するキャッシュミスの数などに大きな違いをもたらす可能性があります。
最初の提案:おそらく、回答を受け入れる前に数分以上待ちたいと思うでしょう:その方法でおそらくより多くの回答を得るでしょう。
第二に、ベンチマークを行うときは、常に最適化をオンにしてコンパイルする必要があります。 _g++ -std=c++11 -O3 -march=native
_のようなものが良い結果をもたらすはずです。
_std::list
_は実際には貧弱なデータ構造であり、それが最良である状況をまだ見つけていません。たとえば、ソートされたデータ構造を維持したい場合を考えてみましょう。 _std::list
_とO(1)
の挿入および削除時間は理想的だと思うかもしれませんが、実際には最適ではありません!
これらのテストの場合、含まれる型は4バイト整数の配列を含む簡単なクラスです。サイズ1、10、および100の_std::array
_をテストします(4、40、および400の要素サイズを取得します)。移動とコピーは同じものなので、_std::array
_を選択しました。配列の最初の要素は、_0
_とstd::numeric_limits<uint32_t>::max()
の間の乱数に初期化されます。これらのいくつか(x軸)の_std::vector
_を作成してから、タイマーを開始します。各要素に対してその_std::vector
_の反復をテストし、それを順番に挿入します(配列の最初の要素で並べ替え、_operator<=
_を使用)。コンパイラーによる最適化による作業の削除を回避するために、ソートされたコンテナーの最初の要素(最後まで判別できない)をファイルに出力し、タイマーを停止します。
これらは、さまざまなサイズの要素に対する私の結果です。
4バイトと40バイトの要素の場合、_std::vector
_はこの挿入でも_std::list
_よりも優れており、どの要素サイズでも_std::vector<unique_ptr>
_よりも_std::list
_を使用した方がよいことがわかります(SOMECODE)__。
一般に、_std::list
_をラップするクラスに対して_std::vector<std::unique_ptr>
_を使用して、コピーする機能(これを修正したい)以外に、値のセマンティクスがあるように見えるようにする理由を思い付くことはできません_value_ptr
_クラスをBoostに送信するか、すぐに追加されない場合は、それについての議論があります)。
追加の注記として、これが比較/ベンチマークゲームではなく実際のコードである場合、私は_std::vector
_バージョンをはるかに異なって書いたでしょう。元のコンテナ全体を直接コピーしてから、_std::sort
_を使用しました。データの局所性の重要性に焦点を当てて、データ構造に関するより完全な分析を書くつもりであり、それを行う「正しい」方法のタイミングを含めます。 (正しいバージョンは、他のすべてのメソッドを水から吹き飛ばし、サイズ400の400,000要素に対して1秒未満で完了します。これは、グラフでテストした要素の10倍の要素です)。
私はすべてをうまく説明したと思います。これらは、私が数か月前に実行したいくつかのテストであり、まだこの件に関するメモを終えていません。
テストは、4 GiB= RAMを搭載したIntel i5マシンで行われました。Fedora17 x64を使用していたと思います。