手元の環境は Ubuntu Linux 24.10, g++ 14.2.0, libstdc++-v3 3.4.33 です。最初に、Linux で提供されている ltrace コマンド(library call tracer)でライブラリ関数が呼び出される回数を測定してみました。(手元の環境では計測に時間がかかるのでデータ数を 500,000 にしています)
ランダムデータ(重複なし)
$ g++ -std=gnu++17 -Wall -Wextra -g insert_random_generated_data_into_map.cc -o insert_random_generated_data_into_map
$ ltrace -c -C ./insert_random_generated_data_into_map
Elapsed Sec 98496 msec
% time seconds usecs/call calls function
------ ----------- ----------- --------- --------------------
28.50 53.956969 53 1000020 operator new(unsigned long)
28.43 53.828525 53 1000000 std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)
28.40 53.765936 53 1000020 operator delete(void*, unsigned long)
14.67 27.771475 53 514938 std::_Rb_tree_decrement(std::_Rb_tree_node_base*)
0.00 0.002167 114 19 memcpy
0.00 0.000279 279 1 std::ios_base::sync_with_stdio(bool)
0.00 0.000185 61 3 std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
0.00 0.000178 89 2 std::chrono::_V2::system_clock::now()
0.00 0.000131 131 1 std::ostream::operator<<(std::ostream& (*)(std::ostream&))
0.00 0.000085 85 1 std::ostream::operator<<(double)
0.00 0.000057 57 1 std::basic_ios<char, std::char_traits<char> >::tie(std::ostream*)
------ ----------- ----------- --------- --------------------
100.00 189.325987 3515006 total
ソート済みデータ(上記のランダムデータをソート)
$ g++ -std=gnu++17 -Wall -Wextra -g insert_sorted_data_into_map.cc -o insert_sorted_data_into_map
$ ltrace -c -C ./insert_sorted_data_into_map
Elapsed Sec 78812 msec
% time seconds usecs/call calls function
------ ----------- ----------- --------- --------------------
30.66 54.264424 54 1000020 operator new(unsigned long)
30.62 54.195150 54 1000000 std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)
30.59 54.134602 54 1000020 operator delete(void*, unsigned long)
8.13 14.394239 54 264953 std::_Rb_tree_decrement(std::_Rb_tree_node_base*)
0.00 0.002236 117 19 memcpy
0.00 0.000256 256 1 std::ios_base::sync_with_stdio(bool)
0.00 0.000248 82 3 std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
0.00 0.000175 87 2 std::chrono::_V2::system_clock::now()
0.00 0.000151 151 1 std::ostream::operator<<(std::ostream& (*)(std::ostream&))
0.00 0.000090 90 1 std::basic_ios<char, std::char_traits<char> >::tie(std::ostream*)
0.00 0.000087 87 1 std::ostream::operator<<(double)
------ ----------- ----------- --------- --------------------
100.00 176.991658 3265021 total
std::_Rb_tree_decrement(std::_Rb_tree_node_base*)
の実行回数に顕著な違いが見られますが、_Rb_tree_decrement()
は build_rnd_vector()
内の unique.insert(rv)
と sort(v.begin(), v.end())
からも呼び出されため、単純に比較することはできません。
mp.insert({ v[i], i });
から _Rb_tree_decrement()
までのバックトレースは以下の通りです。
ここで、_M_get_insert_unique_pos()
内の --__j;
の実体が operator--(int)
となっていて(operator overloading)、operator--(int)
内で Rb_tree_decrement()
が実行されることになります。
c++
1 template < typename _Tp >
2 struct _Rb_tree_iterator
3 {
4 :
5 _Self &
6 operator -- ( ) _GLIBCXX_NOEXCEPT
7 {
8 _M_node = _Rb_tree_decrement ( _M_node ) ;
9 return * this ;
10 }
11 :
12 typedef _Rb_tree_iterator < value_type > iterator ;
13 :
14 }
15 :
16 M_get_insert_unique_pos ( const key_type & __k )
17 {
18 :
19 iterator __j = iterator ( __y ) ;
20 if ( __comp )
21 {
22 if ( __j == begin ( ) )
23 return _Res ( __x , __y ) ;
24 else
25 -- __j ; // => _Rb_tree_iterator::operator--()
26 }
次に _M_get_insert_unique_pos()
の実装について確認しておきたいと思います。なお、cpprefjp C++日本語リファレンスの map::insert
には以下の様に記述されています。
map::insert - cpprefjp C++日本語リファレンス
内部的に map コンテナは、コンストラクト時に指定された比較オブジェクトによって要素を下位から上位へとソートして保持する。
以下のコードの最初の while ループ(while (__x != 0) { ... }
)は見ての通り二分探索(binary search)を行っていて、データの挿入位置を計算しています。ソート済みの一意データの場合、新たに登録されるキーデータはマップオブジェクトに登録されているキーデータよりも常に大きな値を持ちます。そのため __comp
の値は初回登録時を除いて常に false
になります。
c
1 _M_get_insert_unique_pos ( const key_type & __k )
2 {
3 typedef pair < _Base_ptr , _Base_ptr > _Res ;
4 _Link_type __x = _M_begin ( ) ;
5 _Base_ptr __y = _M_end ( ) ;
6 bool __comp = true ;
7 while ( __x != 0 )
8 {
9 __y = __x ;
10 __comp = _M_impl . _M_key_compare ( __k , _S_key ( __x ) ) ;
11 __x = __comp ? _S_left ( __x ) : _S_right ( __x ) ;
12 }
13 iterator __j = iterator ( __y ) ;
14 if ( __comp )
15 {
16 if ( __j == begin ( ) )
17 return _Res ( __x , __y ) ;
18 else
19 -- __j ;
20 }
そこで map::insert()
の実行過程での --__j; (_Rb_tree_decrement())
の実行回数を計測してみます。具体的には GDB を利用します。
count_comp.gdb
set pagination off
# 最初に for (int i = 0; i < v.size(); ++i) {} の直前まで実行しておく
break 44
commands 1
silent
end
run
# 上記の for 文からカウントを開始
set $count = 0
break stl_tree.h:2123 ## --j;
commands 2
silent
end
while(true)
continue
print $count++
end
実行
# ランダムデータ(重複なし)
$ LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/debug/ gdb --batch --command=count_comp.gdb ./insert_random_generated_data_into_map
$1 = 0
$2 = 1
:
$249984 = 249983
$249985 = 249984
Elapsed Sec 91652 msec
[Inferior 1 (process ...) exited normally]
$249986 = 249985
# ソート済みデータ(上記のランダムデータをソート)
$ LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/debug/ gdb --batch --command=count_comp.gdb ./insert_sorted_data_into_map
Elapsed Sec 419 msec
[Inferior 1 (process ...) exited normally]
$1 = 0
ランダムデータの場合は249985回、ソート済みの場合は0回です。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2025/07/22 19:05
2025/07/22 20:46 編集
2025/07/23 18:46