私の実験的なプログラミング言語のインタプリタには、シンボルテーブルがあります。各シンボルは、名前と値で構成されます(値には、string、int、functionなどのタイプがあります)。
最初に、テーブルをベクトルで表現し、指定されたシンボル名が適合するかどうかをチェックするシンボルを繰り返し処理しました。
次に、マップを使用しますが、私の場合は_map<string,symbol>
_の方が、常にベクトルを反復処理するよりも優れていますbut:
この部分を説明するのは少し難しいですが、私は試してみます。
私の言語のプログラムで変数を初めて取得する場合は、もちろん、シンボルテーブル内のその位置を見つける必要があります(今すぐベクトルを使用)。行が実行されるたびに(ループを考えて)ベクトルを反復処理すると、非常に遅くなります(現在のように、Microsoftのバッチとほぼ同じくらい遅くなります)。
したがって、マップを使用して変数を取得できます:_SymbolTable[ myVar.Name ]
_
ただし、次のことを考えてみてください。まだベクトルを使用している変数が最初に見つかった場合、その正確な整数位置をベクトルに格納できます。つまり、次に必要になったときに、インタプリタはそれが「キャッシュ」されていることを認識し、シンボルテーブルで検索せず、SymbolTable.at( myVar.CachedPosition )
のようなことを行います。
今私の(かなり難しい?)質問:
ベクトル内の変数の位置をキャッシュするとともに、シンボルテーブルにベクトルを使用する必要がありますか?
私はむしろ地図を使うべきですか?どうして? []演算子の速度はどれくらいですか?
まったく違うものを使うべきですか?
事実上、いくつかの選択肢があります。
ライブラリが存在します:
vector
を介して実装されたマップのインターフェース。キャッシュの局所性のため、小さいセットまたはフリーズされたセットのマップよりも高速です。評論家
O(log N)
が必要ですが、アイテムがメモリ全体に散在している可能性があるため、キャッシュ戦略でうまく機能しません。find
でO(N)
のパフォーマンスが得られますが、許容できますか?unordered_map
_を使用しないのはなぜですか?それらはO(1)
ルックアップと検索を提供し(定数は高いかもしれませんが)、確かにこのタスクに適しています。 Hash Tables に関するウィキペディアの記事を見ると、利用可能な戦略がたくさんあり、特定の使用パターンに合うものを確実に選択できることがわかります。マップは、シンボルテーブルに使用するのに適しています。しかし、マップの_operator[]
_はそうではありません。一般に、簡単なコードを記述している場合を除き、_operator[]
_の代わりにマップのメンバー関数insert()
およびfind()
を使用する必要があります。 _operator[]
_のセマンティクスはやや複雑であり、探しているシンボルがマップにない場合、ほぼ確実に目的の処理を実行しません。
map
と_unordered_map
_のどちらを選択するかについては、単純なインタープリタ型言語を実装する場合、パフォーマンスの違いが重要になる可能性はほとんどありません。マップを使用する場合、現在のすべての標準C++実装でサポートされることが保証されます。
通常は、シンボルテーブルを使用して、ソースに表示されている名前で変数を検索します。この場合、使用できる名前しかないため、変数のキャッシュされた位置をシンボルテーブルに格納する場所がありません。したがって、map
が適切な選択だと思います。 []
演算子は、マップ内の要素数のログに比例して時間がかかります。遅いことが判明した場合は、std::tr1::unordered_map
のようなハッシュマップを使用できます。
std :: mapのoperator []はO(log(n))時間がかかります。これは非常に効率的であることを意味しますが、それでも何度もルックアップを行うことは避けてください。保存する代わりにインデックス、おそらく値への参照、またはコンテナへのイテレータを格納できますか?これにより、ルックアップを完全に行う必要がなくなります。
ほとんどのインタプリタがコードを解釈するとき、彼らは最初にそれを中間言語にコンパイルします。これらの中間言語は、名前ではなく、インデックスまたはポインターによって変数を参照することがよくあります。
たとえば、Python(C実装)はローカル変数をインデックスによる参照に変更しますが、グローバル変数とクラス変数はハッシュテーブルを使用して名前で参照されます。
コンパイラーの紹介テキストを見ることをお勧めします。
std::map
(O(log(n)))またはハッシュテーブル( "amortized" O(1))が最初の選択肢です-ボトルネックであると判断した場合は、カスタムメカニズムを使用してください。通常、ハッシュを使用するか、入力をトークン化することが最初の最適化です。
プロファイルを作成する前に、ルックアップを分離して、簡単に置き換えてプロファイルできるようにすることが最も重要です。
std::map
は、要素の数が少ない場合は少し遅くなる可能性があります(ただし、実際には問題ではありません)。
「まだベクトルを使用している変数が最初に見つかった場合、その正確な整数位置をベクトルに格納できます。」とあなたは言います。
マップでも同じことができます。find
を使用して変数を検索し、位置の代わりにそれを指すiterator
を保存します。
マップはO(log N)であるため、配列内の位置ルックアップほど高速ではありません。ただし、正確な結果は多くの要因に依存するため、最善のアプローチは、後で実装間で交換できるようにコンテナーとインターフェースすることです。つまり、適切なコンテナで効率的に実装できる「ルックアップ」関数を記述して、さまざまな実装の速度を切り替えて比較できるようにします。
マップの演算子[]はO(log(n))です。ウィキペディアを参照してください: http://en.wikipedia.org/wiki/Map_(C%2B%2B)
シンボルをよく探しているので、地図を使うのは確かに正しいと思います。たぶん、ハッシュマップ( std :: unordered_map )はあなたのパフォーマンスをより良くするかもしれません。
マップの縮尺が大幅に改善されます。これは重要な機能です。ただし、マップを使用する場合は、(ベクトルとは異なり)ポインターと参照を取得できることを忘れないでください。この場合、ベクトルと同じくらい有効に、マップを使用して変数を簡単に「キャッシュ」できます。ここでは、ほぼ間違いなく地図が正しい選択です。
vector
を使用して、最新のシンボルルックアップ結果をキャッシュする問題が発生した場合、シンボルテーブルが実装されていれば、同じこと(最新のルックアップ結果をキャッシュ)を実行できます。 map
として(ただし、map
を使用する場合、キャッシュにはそれほど多くのメリットはないでしょう)。 map
を使用すると、キャッシュされていないシンボルのルックアップがvector
で検索するよりもはるかにパフォーマンスが高くなるという追加の利点があります(vector
が 'でない場合) tソート済み-ソートを複数回実行する必要がある場合、ベクトルをソート済みに保つとコストがかかる可能性があります)。
取る ニールのアドバイス ; map
は通常、シンボルテーブルに適したデータ構造ですが、正しく使用していることを確認する必要があります(誤ってシンボルを追加しないようにする必要があります)。