Skip to content

[libc++] __key_equiv is sometimes 2x expensive#175087

Open
halbi2 wants to merge 7 commits intollvm:mainfrom
halbi2:keyequiv
Open

[libc++] __key_equiv is sometimes 2x expensive#175087
halbi2 wants to merge 7 commits intollvm:mainfrom
halbi2:keyequiv

Conversation

@halbi2
Copy link
Contributor

@halbi2 halbi2 commented Jan 8, 2026

Rename to __key_not_less. Rename and comment on __unique
to __unchecked_unique: ranges::unique has UB but internal
function helper __unchecked_unique does not have UB.

Fixes #175086

This patch reduces the number of comparisons used by that test program from 27 to only 18 so I think it is a large improvement.

@halbi2 halbi2 requested a review from a team as a code owner January 8, 2026 23:14
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jan 8, 2026
@llvmbot
Copy link
Member

llvmbot commented Jan 8, 2026

@llvm/pr-subscribers-libcxx

Author: None (halbi2)

Changes

Maybe __key_equiv is wrongly named. It could be __key_not_greater. The definition for flat_multiset is wrong (should be same as flat_set, not same as flat_map). The definitions for flat_multiset and flat_multimap are unused so I removed them.

Fixes #175086

@huixie please take a look. This patch reduces the number of comparisons used by that test program from 27 to only 18 so I think it is a large improvement.


Full diff: https://github.com/llvm/llvm-project/pull/175087.diff

4 Files Affected:

  • (modified) libcxx/include/__flat_map/flat_map.h (+3-2)
  • (modified) libcxx/include/__flat_map/flat_multimap.h (-9)
  • (modified) libcxx/include/__flat_set/flat_multiset.h (-9)
  • (modified) libcxx/include/__flat_set/flat_set.h (+3-2)
diff --git a/libcxx/include/__flat_map/flat_map.h b/libcxx/include/__flat_map/flat_map.h
index 50487cada24c0..cd97584bbbeed 100644
--- a/libcxx/include/__flat_map/flat_map.h
+++ b/libcxx/include/__flat_map/flat_map.h
@@ -1138,8 +1138,9 @@ class flat_map {
   struct __key_equiv {
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __key_equiv(key_compare __c) : __comp_(__c) {}
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool
-    operator()(const_reference __x, const_reference __y) const {
-      return !__comp_(std::get<0>(__x), std::get<0>(__y)) && !__comp_(std::get<0>(__y), std::get<0>(__x));
+    operator()(const auto& __x, const auto& __y) const {
+      // __x is less or equal to __y because the range is already sorted
+      return !__comp_(std::get<0>(__x), std::get<0>(__y));
     }
     key_compare __comp_;
   };
diff --git a/libcxx/include/__flat_map/flat_multimap.h b/libcxx/include/__flat_map/flat_multimap.h
index 72e3b5f21670c..bd3ec81b98b7f 100644
--- a/libcxx/include/__flat_map/flat_multimap.h
+++ b/libcxx/include/__flat_map/flat_multimap.h
@@ -934,15 +934,6 @@ class flat_multimap {
 
   containers __containers_;
   _LIBCPP_NO_UNIQUE_ADDRESS key_compare __compare_;
-
-  struct __key_equiv {
-    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __key_equiv(key_compare __c) : __comp_(__c) {}
-    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool
-    operator()(const_reference __x, const_reference __y) const {
-      return !__comp_(std::get<0>(__x), std::get<0>(__y)) && !__comp_(std::get<0>(__y), std::get<0>(__x));
-    }
-    key_compare __comp_;
-  };
 };
 
 template <class _KeyContainer, class _MappedContainer, class _Compare = less<typename _KeyContainer::value_type>>
diff --git a/libcxx/include/__flat_set/flat_multiset.h b/libcxx/include/__flat_set/flat_multiset.h
index b2de63bc3038c..4324645fb8445 100644
--- a/libcxx/include/__flat_set/flat_multiset.h
+++ b/libcxx/include/__flat_set/flat_multiset.h
@@ -745,15 +745,6 @@ class flat_multiset {
 
   _KeyContainer __keys_;
   _LIBCPP_NO_UNIQUE_ADDRESS key_compare __compare_;
-
-  struct __key_equiv {
-    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __key_equiv(key_compare __c) : __comp_(__c) {}
-    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool
-    operator()(const_reference __x, const_reference __y) const {
-      return !__comp_(std::get<0>(__x), std::get<0>(__y)) && !__comp_(std::get<0>(__y), std::get<0>(__x));
-    }
-    key_compare __comp_;
-  };
 };
 
 template <class _KeyContainer, class _Compare = less<typename _KeyContainer::value_type>>
diff --git a/libcxx/include/__flat_set/flat_set.h b/libcxx/include/__flat_set/flat_set.h
index 57c3926e33b68..4c24f56423b5c 100644
--- a/libcxx/include/__flat_set/flat_set.h
+++ b/libcxx/include/__flat_set/flat_set.h
@@ -784,8 +784,9 @@ class flat_set {
   struct __key_equiv {
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 __key_equiv(key_compare __c) : __comp_(__c) {}
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 bool
-    operator()(const_reference __x, const_reference __y) const {
-      return !__comp_(__x, __y) && !__comp_(__y, __x);
+    operator()(const auto& __x, const auto& __y) const {
+      // __x is less or equal to __y because the range is already sorted
+      return !__comp_(__x, __y);
     }
     key_compare __comp_;
   };

@github-actions
Copy link

github-actions bot commented Jan 8, 2026

✅ With the latest revision this PR passed the C/C++ code formatter.

@@ -1137,9 +1137,9 @@ class flat_map {

struct __key_equiv {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to reform the functors in this way, we should rename them (maybe to __key_greater_or_equal_to or __key_not_less).

Also, we should post benchmark results somewhere.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit unfortunate that usages of ranges::unique is no longer pedantically correct due to [alg.unique]/9.2.1 - ranges::unique expects the comparator to express an equivalence relation.

I think we should use some other another algorithm. It seems that potential future refactoring of ranges::unique can break the current strategy.

Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a good idea, at least as-is. ranges::unique requires the predicate to be an equivalence relation, which the new definition of __key_equiv isn't, which means that this patch introduces UB.

Rename to __key_not_less. Rename and comment on __unique
to __unchecked_unique: ranges::unique has UB but internal
function helper __unchecked_unique does not have UB.

Fixes llvm#175086
@halbi2 halbi2 changed the title [libc++] __key_equiv is sometimes unused, sometimes 2x expensive [libc++] __key_equiv is sometimes 2x expensive Jan 9, 2026
@huixie90 huixie90 self-assigned this Jan 10, 2026
template <class _AlgPolicy, class _Iter, class _Sent, class _BinaryPredicate>
[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 std::pair<_Iter, _Iter>
__unique(_Iter __first, _Sent __last, _BinaryPredicate&& __pred) {
__unchecked_unique(_Iter __first, _Sent __last, _BinaryPredicate&& __pred) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is unclear to me what the meaning of this unchecked is. I stiff prefer the old name __unique , which matches our convention for all algorithms. __foo is the internal function that is shared between std::foo and std::ranges::foo.

I think what others concern is not only about equivalent_relation concept, it is also about the semantic of this algorithm. Even the c++17 version has a "equivalence relation" precondition. It happens that the implementation is currently invoking the predicate only in one direction. if the implementation of unique one day does something different in the future, this could break.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The meaning of this is that this internal helper algorithm does not have the semantic of the std::unique algorithm, that checks where the predicate is an equivalence relation and otherwise UB. This algorithm is unchecked, does not require equivalence relation. There it is different to even the c++17 version. This helper algorithm does not have the same semantics as std::unique or ranges::unique, therefore it should not have the same name, so I have changed its name accordingly.

"if the implementation of unique one day does something different in the future, this could break" -- I do not understand your meaning. This unchecked algorithm is libc++'s implementation of unique, is it not? How could it do something different, without breaking libc++?

This helper function could be named __oneway_unique -- the predicate is evaluated only one way -- or __unconstrained_unique -- it has no constraints -- instead of __unchecked_unique. I only copied unchecked from other places in libc++ where a preconditioned helper is used.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a huge fan of the naming either. To me this currently implies that we simply don't check some semantic requirement/precondition, which is not the case. However, I do agree with @halbi2 that it shouldn't be __unique, since that implies the same semantics as {std,ranges}::unique, which we don't want. This function actually has different semantics, or at least preconditions. WDYT about __unique_sorted_range?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@philnik777 I have renamed the function as you asked. Can you merge this improvement please?

Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide benchmark results as well and remove any @ mentions from the commit message.

template <class _AlgPolicy, class _Iter, class _Sent, class _BinaryPredicate>
[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 std::pair<_Iter, _Iter>
__unique(_Iter __first, _Sent __last, _BinaryPredicate&& __pred) {
__unchecked_unique(_Iter __first, _Sent __last, _BinaryPredicate&& __pred) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a huge fan of the naming either. To me this currently implies that we simply don't check some semantic requirement/precondition, which is not the case. However, I do agree with @halbi2 that it shouldn't be __unique, since that implies the same semantics as {std,ranges}::unique, which we don't want. This function actually has different semantics, or at least preconditions. WDYT about __unique_sorted_range?

@halbi2
Copy link
Contributor Author

halbi2 commented Jan 12, 2026

Please provide benchmark results as well and remove any @ mentions from the commit message.

Currently there is no benchmark for constructing a flat_map with nontrivial comparator. Do you want to add one?
I changed int to string and ran the benchmark that exists. It has much noise, I don't think it is useful.

This patch obviously does not do anything except remove excess calls to the comparator. I do not think a benchmark will show much data.

Benchmark                                                                                         Baseline    Candidate    Difference    % Difference
---------------------------------------------------------------------------------------------  -----------  -----------  ------------  --------------
std::flat_map<string,_int>::clear()/0                                                               590.74       599.12          8.38           1.42%
std::flat_map<string,_int>::clear()/1024                                                          38884.00     37387.18      -1496.81          -3.85%
std::flat_map<string,_int>::clear()/32                                                             1629.11      1539.96        -89.16          -5.47%
std::flat_map<string,_int>::clear()/8192                                                         351137.53    362769.64      11632.10           3.31%
std::flat_map<string,_int>::contains(key)_(existent)/0                                                0.12         0.12         -0.00          -1.40%
std::flat_map<string,_int>::contains(key)_(existent)/1024                                           981.69       940.37        -41.31          -4.21%
std::flat_map<string,_int>::contains(key)_(existent)/32                                             657.58       607.93        -49.65          -7.55%
std::flat_map<string,_int>::contains(key)_(existent)/8192                                          1106.98      1135.09         28.11           2.54%
std::flat_map<string,_int>::contains(key)_(non-existent)/0                                          116.81       118.23          1.42           1.21%
std::flat_map<string,_int>::contains(key)_(non-existent)/1024                                       821.26       772.37        -48.89          -5.95%
std::flat_map<string,_int>::contains(key)_(non-existent)/32                                         471.08       501.28         30.20           6.41%
std::flat_map<string,_int>::contains(key)_(non-existent)/8192                                       992.83       943.03        -49.79          -5.02%
std::flat_map<string,_int>::count(key)_(existent)/0                                                   0.12         0.12         -0.01          -6.30%
std::flat_map<string,_int>::count(key)_(existent)/1024                                              958.01       921.05        -36.96          -3.86%
std::flat_map<string,_int>::count(key)_(existent)/32                                                662.44       621.16        -41.29          -6.23%
std::flat_map<string,_int>::count(key)_(existent)/8192                                             1101.11      1098.67         -2.44          -0.22%
std::flat_map<string,_int>::count(key)_(non-existent)/0                                             117.73       114.49         -3.24          -2.75%
std::flat_map<string,_int>::count(key)_(non-existent)/1024                                          777.69       764.53        -13.16          -1.69%
std::flat_map<string,_int>::count(key)_(non-existent)/32                                            487.16       465.19        -21.97          -4.51%
std::flat_map<string,_int>::count(key)_(non-existent)/8192                                         1031.84       926.94       -104.90         -10.17%
std::flat_map<string,_int>::ctor(&&,_different_allocs)/0                                            265.00       250.97        -14.04          -5.30%
std::flat_map<string,_int>::ctor(&&,_different_allocs)/1024                                       47560.82     49152.48       1591.66           3.35%
std::flat_map<string,_int>::ctor(&&,_different_allocs)/32                                          2117.63      2027.65        -89.98          -4.25%
std::flat_map<string,_int>::ctor(&&,_different_allocs)/8192                                      378202.99    368666.05      -9536.94          -2.52%
std::flat_map<string,_int>::ctor(const&)/0                                                           56.98        55.25         -1.73          -3.04%
std::flat_map<string,_int>::ctor(const&)/1024                                                     65911.42     68691.56       2780.14           4.22%
std::flat_map<string,_int>::ctor(const&)/32                                                        1514.29      1569.16         54.88           3.62%
std::flat_map<string,_int>::ctor(const&)/8192                                                    605530.25    583758.97     -21771.28          -3.60%
std::flat_map<string,_int>::ctor(const&,_alloc)/0                                                   127.33       122.06         -5.27          -4.14%
std::flat_map<string,_int>::ctor(const&,_alloc)/1024                                              67234.49     63651.53      -3582.96          -5.33%
std::flat_map<string,_int>::ctor(const&,_alloc)/32                                                 1585.53      1457.35       -128.18          -8.08%
std::flat_map<string,_int>::ctor(const&,_alloc)/8192                                             605296.83    570291.97     -35004.86          -5.78%
std::flat_map<string,_int>::ctor(iterator,_iterator)_(sorted_sequence)/0                             63.75        61.66         -2.10          -3.29%
std::flat_map<string,_int>::ctor(iterator,_iterator)_(sorted_sequence)/1024                      692279.94    679066.76     -13213.18          -1.91%
std::flat_map<string,_int>::ctor(iterator,_iterator)_(sorted_sequence)/32                         25053.25     24053.32       -999.94          -3.99%
std::flat_map<string,_int>::ctor(iterator,_iterator)_(sorted_sequence)/8192                     5582283.21   5460042.96    -122240.25          -2.19%
std::flat_map<string,_int>::ctor(iterator,_iterator)_(unsorted_sequence)/0                           63.45        62.56         -0.89          -1.40%
std::flat_map<string,_int>::ctor(iterator,_iterator)_(unsorted_sequence)/1024                   2994958.34   2912671.22     -82287.12          -2.75%
std::flat_map<string,_int>::ctor(iterator,_iterator)_(unsorted_sequence)/32                       57840.71     57028.37       -812.34          -1.40%
std::flat_map<string,_int>::ctor(iterator,_iterator)_(unsorted_sequence)/8192                  27461977.73  26703992.16    -757985.57          -2.76%
std::flat_map<string,_int>::equal_range(key)_(existent)/0                                             0.12         0.12          0.00           2.72%
std::flat_map<string,_int>::equal_range(key)_(existent)/1024                                        937.22       959.63         22.41           2.39%
std::flat_map<string,_int>::equal_range(key)_(existent)/32                                          628.30       643.78         15.48           2.46%
std::flat_map<string,_int>::equal_range(key)_(existent)/8192                                       1123.54      1118.87         -4.66          -0.42%
std::flat_map<string,_int>::equal_range(key)_(non-existent)/0                                       128.55       116.19        -12.35          -9.61%
std::flat_map<string,_int>::equal_range(key)_(non-existent)/1024                                    819.22       825.72          6.50           0.79%
std::flat_map<string,_int>::equal_range(key)_(non-existent)/32                                      493.11       482.90        -10.21          -2.07%
std::flat_map<string,_int>::equal_range(key)_(non-existent)/8192                                   1022.63       944.25        -78.37          -7.66%
std::flat_map<string,_int>::erase(iterator)/0                                                       180.85       197.17         16.32           9.02%
std::flat_map<string,_int>::erase(iterator)/1024                                                    533.73       469.38        -64.35         -12.06%
std::flat_map<string,_int>::erase(iterator)/32                                                      213.00       221.25          8.25           3.87%
std::flat_map<string,_int>::erase(iterator)/8192                                                   2360.45      2253.53       -106.92          -4.53%
std::flat_map<string,_int>::erase(iterator,_iterator)_(erase_half_the_container)/0                  691.51       682.74         -8.78          -1.27%
std::flat_map<string,_int>::erase(iterator,_iterator)_(erase_half_the_container)/1024             21687.71     21031.25       -656.46          -3.03%
std::flat_map<string,_int>::erase(iterator,_iterator)_(erase_half_the_container)/32                1253.67      1248.25         -5.42          -0.43%
std::flat_map<string,_int>::erase(iterator,_iterator)_(erase_half_the_container)/8192            284780.80    277822.76      -6958.04          -2.44%
std::flat_map<string,_int>::erase(key)_(existent)/0                                                 593.76       498.02        -95.75         -16.13%
std::flat_map<string,_int>::erase(key)_(existent)/1024                                             1582.32      1525.57        -56.75          -3.59%
std::flat_map<string,_int>::erase(key)_(existent)/32                                                838.18       883.54         45.35           5.41%
std::flat_map<string,_int>::erase(key)_(existent)/8192                                             3549.49      3699.46        149.97           4.23%
std::flat_map<string,_int>::erase(key)_(non-existent)/0                                             130.74       124.51         -6.23          -4.77%
std::flat_map<string,_int>::erase(key)_(non-existent)/1024                                          848.83       779.13        -69.69          -8.21%
std::flat_map<string,_int>::erase(key)_(non-existent)/32                                            527.28       491.55        -35.73          -6.78%
std::flat_map<string,_int>::erase(key)_(non-existent)/8192                                         1041.35       974.18        -67.18          -6.45%
std::flat_map<string,_int>::find(key)_(existent)/0                                                    0.12         0.12         -0.00          -2.68%
std::flat_map<string,_int>::find(key)_(existent)/1024                                               957.81       894.33        -63.48          -6.63%
std::flat_map<string,_int>::find(key)_(existent)/32                                                 583.80       579.81         -3.99          -0.68%
std::flat_map<string,_int>::find(key)_(existent)/8192                                              1143.78      1072.45        -71.33          -6.24%
std::flat_map<string,_int>::find(key)_(non-existent)/0                                              101.62        96.12         -5.51          -5.42%
std::flat_map<string,_int>::find(key)_(non-existent)/1024                                           786.35       751.08        -35.27          -4.48%
std::flat_map<string,_int>::find(key)_(non-existent)/32                                             465.37       427.69        -37.68          -8.10%
std::flat_map<string,_int>::find(key)_(non-existent)/8192                                           970.04       912.61        -57.43          -5.92%
std::flat_map<string,_int>::insert(hint,_value)_(bad_hint,_end)/0                                   363.30       398.06         34.76           9.57%
std::flat_map<string,_int>::insert(hint,_value)_(bad_hint,_end)/1024                               1255.32      1239.90        -15.42          -1.23%
std::flat_map<string,_int>::insert(hint,_value)_(bad_hint,_end)/32                                  908.20       918.57         10.37           1.14%
std::flat_map<string,_int>::insert(hint,_value)_(bad_hint,_end)/8192                               1429.12      1365.06        -64.06          -4.48%
std::flat_map<string,_int>::insert(hint,_value)_(bad_hint,_middle)/0                                399.22       358.38        -40.84         -10.23%
std::flat_map<string,_int>::insert(hint,_value)_(bad_hint,_middle)/1024                            1680.73      1589.82        -90.92          -5.41%
std::flat_map<string,_int>::insert(hint,_value)_(bad_hint,_middle)/32                              1016.22       960.08        -56.14          -5.52%
std::flat_map<string,_int>::insert(hint,_value)_(bad_hint,_middle)/8192                            3951.95      3656.63       -295.31          -7.47%
std::flat_map<string,_int>::insert(hint,_value)_(good_hint,_end)/0                                  353.71       322.89        -30.83          -8.71%
std::flat_map<string,_int>::insert(hint,_value)_(good_hint,_end)/1024                               441.88       437.78         -4.09          -0.93%
std::flat_map<string,_int>::insert(hint,_value)_(good_hint,_end)/32                                 434.87       432.31         -2.55          -0.59%
std::flat_map<string,_int>::insert(hint,_value)_(good_hint,_end)/8192                               480.15       473.54         -6.61          -1.38%
std::flat_map<string,_int>::insert(hint,_value)_(good_hint,_middle)/0                               359.42       338.21        -21.21          -5.90%
std::flat_map<string,_int>::insert(hint,_value)_(good_hint,_middle)/1024                            930.07       890.40        -39.67          -4.27%
std::flat_map<string,_int>::insert(hint,_value)_(good_hint,_middle)/32                              618.12       601.21        -16.90          -2.73%
std::flat_map<string,_int>::insert(hint,_value)_(good_hint,_middle)/8192                           2945.02      2780.37       -164.65          -5.59%
std::flat_map<string,_int>::insert(iterator,_iterator)_(all_new_keys)/0                             638.95       597.50        -41.45          -6.49%
std::flat_map<string,_int>::insert(iterator,_iterator)_(all_new_keys)/1024                      1055687.51    973273.68     -82413.83          -7.81%
std::flat_map<string,_int>::insert(iterator,_iterator)_(all_new_keys)/32                          38414.39     35752.63      -2661.76          -6.93%
std::flat_map<string,_int>::insert(iterator,_iterator)_(all_new_keys)/8192                      8603637.19   7900420.66    -703216.53          -8.17%
std::flat_map<string,_int>::insert(iterator,_iterator)_(half_new_keys)/0                            638.65       619.23        -19.42          -3.04%
std::flat_map<string,_int>::insert(iterator,_iterator)_(half_new_keys)/1024                     1514374.78   1428000.37     -86374.41          -5.70%
std::flat_map<string,_int>::insert(iterator,_iterator)_(half_new_keys)/32                         52888.70     49406.42      -3482.28          -6.58%
std::flat_map<string,_int>::insert(iterator,_iterator)_(half_new_keys)/8192                    11971636.83  11396667.98    -574968.85          -4.80%
std::flat_map<string,_int>::insert(iterator,_iterator)_(product_iterator_from_same_type)/0          755.78       744.22        -11.55          -1.53%
std::flat_map<string,_int>::insert(iterator,_iterator)_(product_iterator_from_same_type)/1024    590417.41    576158.59     -14258.82          -2.42%
std::flat_map<string,_int>::insert(iterator,_iterator)_(product_iterator_from_same_type)/32       22319.97     21746.07       -573.90          -2.57%
std::flat_map<string,_int>::insert(iterator,_iterator)_(product_iterator_from_same_type)/8192   4909069.14   4751913.52    -157155.63          -3.20%
std::flat_map<string,_int>::insert(iterator,_iterator)_(product_iterator_from_zip_view)/0          1058.02      1030.10        -27.92          -2.64%
std::flat_map<string,_int>::insert(iterator,_iterator)_(product_iterator_from_zip_view)/1024     596761.19    573785.34     -22975.85          -3.85%
std::flat_map<string,_int>::insert(iterator,_iterator)_(product_iterator_from_zip_view)/32        22686.13     22020.17       -665.96          -2.94%
std::flat_map<string,_int>::insert(iterator,_iterator)_(product_iterator_from_zip_view)/8192    4847577.00   4718445.32    -129131.68          -2.66%
std::flat_map<string,_int>::insert(value)_(already_present)/0                                       339.16       312.15        -27.01          -7.96%
std::flat_map<string,_int>::insert(value)_(already_present)/1024                                    808.29       798.16        -10.13          -1.25%
std::flat_map<string,_int>::insert(value)_(already_present)/32                                      578.24       552.65        -25.59          -4.43%
std::flat_map<string,_int>::insert(value)_(already_present)/8192                                   1064.18      1129.26         65.09           6.12%
std::flat_map<string,_int>::insert(value)_(new_value)/0                                             303.19       283.60        -19.59          -6.46%
std::flat_map<string,_int>::insert(value)_(new_value)/1024                                          998.58      1032.35         33.77           3.38%
std::flat_map<string,_int>::insert(value)_(new_value)/32                                            649.51       665.26         15.75           2.43%
std::flat_map<string,_int>::insert(value)_(new_value)/8192                                         1251.36      1250.43         -0.93          -0.07%
std::flat_map<string,_int>::insert_or_assign(key,_value)_(already_present)/0                        240.62       253.70         13.08           5.44%
std::flat_map<string,_int>::insert_or_assign(key,_value)_(already_present)/1024                     867.07       815.48        -51.59          -5.95%
std::flat_map<string,_int>::insert_or_assign(key,_value)_(already_present)/32                       516.17       524.31          8.14           1.58%
std::flat_map<string,_int>::insert_or_assign(key,_value)_(already_present)/8192                    1131.11      1141.48         10.37           0.92%
std::flat_map<string,_int>::insert_or_assign(key,_value)_(new_value)/0                              279.55       266.72        -12.83          -4.59%
std::flat_map<string,_int>::insert_or_assign(key,_value)_(new_value)/1024                          1023.11      1053.65         30.54           2.99%
std::flat_map<string,_int>::insert_or_assign(key,_value)_(new_value)/32                             670.16       639.14        -31.02          -4.63%
std::flat_map<string,_int>::insert_or_assign(key,_value)_(new_value)/8192                          1140.09      1117.93        -22.16          -1.94%
std::flat_map<string,_int>::lower_bound(key)_(existent)/0                                             0.13         0.12         -0.01          -7.24%
std::flat_map<string,_int>::lower_bound(key)_(existent)/1024                                        823.10       766.19        -56.91          -6.91%
std::flat_map<string,_int>::lower_bound(key)_(existent)/32                                          505.05       473.30        -31.75          -6.29%
std::flat_map<string,_int>::lower_bound(key)_(existent)/8192                                        966.61       946.40        -20.20          -2.09%
std::flat_map<string,_int>::lower_bound(key)_(non-existent)/0                                        70.72        73.15          2.43           3.44%
std::flat_map<string,_int>::lower_bound(key)_(non-existent)/1024                                    761.25       696.88        -64.36          -8.46%
std::flat_map<string,_int>::lower_bound(key)_(non-existent)/32                                      416.21       411.36         -4.85          -1.17%
std::flat_map<string,_int>::lower_bound(key)_(non-existent)/8192                                    911.43       882.58        -28.85          -3.17%
std::flat_map<string,_int>::operator=(const&)_(into_cleared_Container)/0                            212.07       204.90         -7.18          -3.39%
std::flat_map<string,_int>::operator=(const&)_(into_cleared_Container)/1024                       65793.37     64399.32      -1394.05          -2.12%
std::flat_map<string,_int>::operator=(const&)_(into_cleared_Container)/32                          1507.22      1499.38         -7.84          -0.52%
std::flat_map<string,_int>::operator=(const&)_(into_cleared_Container)/8192                      557711.14    621830.15      64119.01          11.50%
std::flat_map<string,_int>::operator=(const&)_(into_partially_populated_Container)/0                210.16       217.63          7.47           3.55%
std::flat_map<string,_int>::operator=(const&)_(into_partially_populated_Container)/1024           64555.26     66344.89       1789.62           2.77%
std::flat_map<string,_int>::operator=(const&)_(into_partially_populated_Container)/32              1503.43      1384.12       -119.30          -7.94%
std::flat_map<string,_int>::operator=(const&)_(into_partially_populated_Container)/8192          557039.83    599912.08      42872.24           7.70%
std::flat_map<string,_int>::operator=(const&)_(into_populated_Container)/0                          173.96       176.51          2.55           1.46%
std::flat_map<string,_int>::operator=(const&)_(into_populated_Container)/1024                     17364.29     16314.46      -1049.83          -6.05%
std::flat_map<string,_int>::operator=(const&)_(into_populated_Container)/32                         611.38       586.36        -25.03          -4.09%
std::flat_map<string,_int>::operator=(const&)_(into_populated_Container)/8192                    171517.19    163477.69      -8039.50          -4.69%
std::flat_map<string,_int>::upper_bound(key)_(existent)/0                                             0.12         0.12         -0.00          -0.55%
std::flat_map<string,_int>::upper_bound(key)_(existent)/1024                                        783.21       774.83         -8.38          -1.07%
std::flat_map<string,_int>::upper_bound(key)_(existent)/32                                          483.94       510.19         26.25           5.42%
std::flat_map<string,_int>::upper_bound(key)_(existent)/8192                                        942.93       988.85         45.93           4.87%
std::flat_map<string,_int>::upper_bound(key)_(non-existent)/0                                        66.70        65.22         -1.48          -2.22%
std::flat_map<string,_int>::upper_bound(key)_(non-existent)/1024                                    709.64       678.24        -31.40          -4.43%
std::flat_map<string,_int>::upper_bound(key)_(non-existent)/32                                      409.89       387.78        -22.10          -5.39%
std::flat_map<string,_int>::upper_bound(key)_(non-existent)/8192                                    880.63       868.00        -12.62          -1.43%
Geomean                                                                                            1790.95      1743.00        -47.95          -2.68%

@huixie90
Copy link
Member

_(existent)/0 0.12 0.12 -0.01 -6.30%
std::flat_map<string,int>::count(key)(existent)/1024 958.01 921.05 -36.96 -3.86%
std::flat_map<string,int>::count(key)(existent)/32 662.44 621.16 -41.29 -6.23%
std::flat_map<string,int>::count(key)(existent)/8192 1101.11 1098.67 -2.44 -0.22%
std::flat_map<string,int>::count(key)(non-existent)

how can we explain the consistent speed up in the count, lower_bound, and upper_bound that don't seem to be impacted by the change?

@halbi2
Copy link
Contributor Author

halbi2 commented Jan 17, 2026

how can we explain the consistent speed up in the count, lower_bound, and upper_bound

The benchmark is not very good, it is noisy. Perhaps my computer is faster on the second run. I do not care about the benchmark because the change is very obvious and trivial.

@huixie90 could you help me merge this please?

@huixie90
Copy link
Member

how can we explain the consistent speed up in the count, lower_bound, and upper_bound

The benchmark is not very good, it is noisy. Perhaps my computer is faster on the second run. I do not care about the benchmark because the change is very obvious and trivial.

@huixie90 could you help me merge this please?

I think I am happy with the current state of the PR. I will wait for @philnik777 to confirm if his concerns have been addressed

[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator
unique(_ForwardIterator __first, _ForwardIterator __last, _BinaryPredicate __pred) {
return std::__unique<_ClassicAlgPolicy>(std::move(__first), std::move(__last), __pred).first;
return std::__unique_sorted_range<_ClassicAlgPolicy>(std::move(__first), std::move(__last), __pred).first;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems incorrect conceptually. std::unique isn't guaranteed to be given a sorted range.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand. Do you want me to duplicate the code, or what? Please instruct me what to do.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__deduplicate_on_non_front_back_relation (which means deduplicating when !pred(front_element, back_element)) seems to be a less misleading name, but I'm not sure whether it's too complicated to read.

It's a bit hard to name it because we want to allow the relation to be equivalence or ordering.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicating the code is one option. Another option would be a somewhat different name. Maybe something like __deduplicate_if? IDK. I'm kinda tempted to say we should just introduce a flat_{set,map}-specific algorithm that takes the underlying container and destroys the non-unique elements. That would give us an API to improve this more (e.g. by making use of trivially relocatable concepts).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@philnik777: Can you explain about "making use of trivially relocatable concepts"? I don't see what you mean.

What can I do to help you merge this very simple performance improvement to your library?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@frederick-vs-ja can you help me land this patch please?

@halbi2
Copy link
Contributor Author

halbi2 commented Feb 10, 2026

@philnik777 ping

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[libc++] flat_set uses 2x as many comparisons as it should

5 participants