マップに値を割り当てる最も効率的な方法はどれですか?それとも、それらはすべて同じコードに最適化されていますか(ほとんどの最新のコンパイラーで)?
// 1) Assignment using array index notation
Foo["Bar"] = 12345;
// 2) Assignment using member function insert() and STL pair
Foo.insert(std::pair<string,int>("Bar", 12345));
// 3) Assignment using member function insert() and "value_type()"
Foo.insert(map<string,int>::value_type("Bar", 12345));
// 4) Assignment using member function insert() and "make_pair()"
Foo.insert(std::make_pair("Bar", 12345));
(コンパイラの出力をベンチマークして確認できることはわかっていますが、この質問が発生しました。手元にあるのは携帯電話だけです... hehe)
まず、_[]
_とinsert
の間には意味上の違いがあります。
[]
_は古い値(存在する場合)を置き換えますinsert
willignore新しい値(古い値がすでに存在する場合)したがって、2つを比較することは一般的に役に立たない。
インサートバージョンについて:
std::map<std::string, int>::value_type
_is_std::pair<std::string const, int>
_なので、3と4の間に(重要な)違いはありませんstd::make_pair("Bar", 12345)
はstd::pair<std::string, int>("Bar", 12345)
よりも安価です。これは、_std::string
_型がコピーで実行する操作を備えた本格的なクラスであり、_"Bar"
_が単なる文字列リテラルであるためです(したがって、ポインタコピー);ただし、最後に_std::string
_...を作成する必要があるため、コンパイラの品質によって異なります。一般的に、私はお勧めします:
[]
_更新用insert(std::make_pair())
_std::make_pair
_は短いだけでなく、DRYガイドライン:自分を繰り返さないでください。
ただし、完全を期すために、最速(かつ最も簡単)は emplace
(C++ 11対応):
_map.emplace("Bar", 12345);
_
その動作はinsert
の動作ですが、新しい要素をインプレースで構築します。
1)std::map::operator[]
が最初にデフォルトであるため、他のメソッドよりも少し遅い場合があります-オブジェクトがまだ存在しない場合は作成し、次にoperator=
を使用して目的の値を設定できる参照を返します。つまり、2つの操作。
2-4)map::value_type
は同じタイプのstd::pair
とtypedefであるため、同等である必要があります。したがって、make_pair
も同等です。コンパイラはこれらを同じように扱う必要があります。
また、map::lower_bound
を使用して最初に場所のヒントを取得することにより、存在を確認し(たとえば、存在するかどうかに応じて特別なロジックを実行する)、挿入する必要がある場合は、パフォーマンスをさらに向上させることができます。要素はである必要があるため、map::insert
はmap
全体を再度検索する必要はありません。
// get the iterator to where the key *should* be if it existed:
std::map::iterator hint = mymap.lower_bound(key);
if (hint == mymap.end() || mymap.key_comp()(key, hint->first)) {
// key didn't exist in map
// special logic A here...
// insert at the correct location
mymap.insert(hint, make_pair(key, new_value));
} else {
// key exists in map already
// special logic B here...
// just update value
hint->second = new_value;
}
あなたの最初の可能性:Foo["Bar"] = 12345;
のセマンティクスは他のものとは多少異なります-存在しない場合は新しいオブジェクトを挿入しますが(他のオブジェクトと同様)、存在しない場合は上書き現在のコンテンツ(他のオブジェクトがinsert
そのキーがすでに存在する場合は失敗します)。
速度に関しては、他の速度よりも遅くなる可能性があります。新しいオブジェクトを挿入すると、指定されたキーとデフォルトで作成されたvalue_typeを使用してペアが作成され、その後、正しいvalue_typeが割り当てられます。他のすべては、正しいキーと値のペアを構築し、そのオブジェクトを挿入します。ただし、公平を期すために、私の経験では、違いが重要になることはめったにありません(古いコンパイラでは、より重要でしたが、新しいコンパイラではごくわずかです)。
次の2つは互いに同等です。同じタイプに名前を付けるために2つの異なる方法を使用しているだけです。実行時では、それらの間にまったく違いはありません。
4つ目は、理論的にはcould追加レベルの関数呼び出しを伴うテンプレート関数(make_pair)を使用します。ただし、コンパイラが完全にno最適化(特にインライン化)を実行するように注意した場合を除いて、これとの実際の違いを見て非常に驚きます。
結論:最初のものは他のものより少し遅くなることがよくあります(しかし常にではなく、それほどではありません)。他の3つはほとんどの場合等しくなります(たとえば、通常、妥当なコンパイラーが3つすべてに対して同一のコードを生成することを期待します)。
すでにいくつかの良い答えがありますが、私は簡単なベンチマークを実行したほうがよいと思いました。それぞれを500万回実行し、c ++ 11のクロノを使用して所要時間を測定しました。
コードは次のとおりです。
#include <string>
#include <map>
#include <chrono>
#include <cstdio>
// 5 million
#define times 5000000
int main()
{
std::map<std::string, int> foo1, foo2, foo3, foo4, foo5;
std::chrono::steady_clock::time_point timeStart, timeEnd;
int x = 0;
// 1) Assignment using array index notation
timeStart = std::chrono::steady_clock::now();
for (x = 0; x <= times; x++)
{
foo1[std::to_string(x)] = 12345;
}
timeEnd = std::chrono::steady_clock::now();
printf("1) took %i milliseconds\n", (unsigned long long)std::chrono::duration_cast<std::chrono::milliseconds>(timeEnd-timeStart).count());
// 2) Assignment using member function insert() and STL pair
timeStart = std::chrono::steady_clock::now();
for (x = 0; x <= times; x++)
{
foo2.insert(std::pair<std::string, int>(std::to_string(x), 12345));
}
timeEnd = std::chrono::steady_clock::now();
printf("2) took %i milliseconds\n", (unsigned long long)std::chrono::duration_cast<std::chrono::milliseconds>(timeEnd-timeStart).count());
// 3) Assignment using member function insert() and "value_type()"
timeStart = std::chrono::steady_clock::now();
for (x = 0; x <= times; x++)
{
foo3.insert(std::map<std::string, int>::value_type(std::to_string(x), 12345));
}
timeEnd = std::chrono::steady_clock::now();
printf("3) took %i milliseconds\n", (unsigned long long)std::chrono::duration_cast<std::chrono::milliseconds>(timeEnd-timeStart).count());
// 4) Assignment using member function insert() and "make_pair()"
timeStart = std::chrono::steady_clock::now();
for (x = 0; x <= times; x++)
{
foo4.insert(std::make_pair(std::to_string(x), 12345));
}
timeEnd = std::chrono::steady_clock::now();
printf("4) took %i milliseconds\n", (unsigned long long)std::chrono::duration_cast<std::chrono::milliseconds>(timeEnd-timeStart).count());
// 5) Matthieu M.'s suggestion of C++11's emplace
timeStart = std::chrono::steady_clock::now();
for (x = 0; x <= times; x++)
{
foo5.emplace(std::to_string(x), 12345);
}
timeEnd = std::chrono::steady_clock::now();
printf("5) took %i milliseconds\n", (unsigned long long)std::chrono::duration_cast<std::chrono::milliseconds>(timeEnd-timeStart).count());
return 0;
}
500万回の反復の出力は次のとおりです。
1) took 23448 milliseconds
2) took 22854 milliseconds
3) took 22372 milliseconds
4) took 22988 milliseconds
5) took 21356 milliseconds
GCCバージョン:
g++ (Built by MinGW-builds project) 4.8.0 20121225 (experimental)
私のマシン:
Intel i5-3570k overclocked at 4.6 GHz
EDIT1:コードを修正し、ベンチマークをやり直しました。
EDIT2:C++ 11のemplaceに対するMatthieuM。の提案を追加しました。彼は正しいです、emplaceが最速です
そのキーの場所にオブジェクトがない場合は、次のようにします。
_std::map::emplace
_ が最も効率的です。 insert
は2番目です(ただし、非常に近くなります)。 _[]
_は最も効率が悪いです。
_[]
_、そこにオブジェクトがない場合、トリビアルはオブジェクトを構築します。次に、_operator=
_を呼び出します。
insert
は、_std::pair
_引数に対してコピーコンストラクター呼び出しを行います。
ただし、マップの場合、map.insert( make_pair( std::move(key), std::move(value) ) )
はmap.emplace( std::move(key), std::move(value) )
に近くなります。
キーの場所にオブジェクトがある場合、_[]
_は_operator=
_を呼び出し、insert
/emplace
は古いオブジェクトを破棄して新しいオブジェクトを作成します。この場合、_[]
_は簡単に安くなる可能性があります。
結局のところ、それはあなたの_operator=
_対コピー構築対トリビアル構築対デストラクタのコストがあなたのキーと価値に対して何であるかに依存します。
_std::map
_のツリー構造内で物事を検索する実際の作業は、まったく同じに近いため、面白くありません。
3番目のものが最良の選択(IMHO)ですが、2、3、および4は同じです。
// 3) Assignment using member function insert() and "value_type()"
Foo.insert(map<string,int>::value_type("Bar", 12345));
3番目のものが最良の選択であると思う理由:値を挿入するために1つだけ操作を実行しています:挿入するだけで(まあ、検索もあります)、値が挿入されたかどうかを知ることができます、戻り値のsecond
メンバーをチェックすると、実装は値を上書きしないことを許可します。
value_type
の使用にも利点があります。マップされたタイプやキータイプを知る必要がないため、テンプレートプログラミングで役立ちます。
最悪(IMHO)は最初のものです:
// 1) Assignment using array index notation
Foo["Bar"] = 12345;
オブジェクトを作成してそのオブジェクトへの参照を返すstd::map::operator[]
を呼び出すと、マップされたオブジェクトoperator =
が呼び出されます。挿入には2つの操作を実行します。1つ目は挿入、2つ目は割り当てです。
また、別の問題があります。値が挿入されたか上書きされたかがわかりません。