手元の環境は 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