-
Notifications
You must be signed in to change notification settings - Fork 14.9k
[libc++] Optimize map::insert_or_assign #155816
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
+54
−12
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2dbac71
to
61e7688
Compare
ldionne
reviewed
Aug 28, 2025
libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
Outdated
Show resolved
Hide resolved
@llvm/pr-subscribers-libcxx Author: Nikolas Klauser (philnik777) Changes
Full diff: https://github.com/llvm/llvm-project/pull/155816.diff 3 Files Affected:
diff --git a/libcxx/docs/ReleaseNotes/22.rst b/libcxx/docs/ReleaseNotes/22.rst
index 4ad5452cc29cc..cd8171dc5d1e8 100644
--- a/libcxx/docs/ReleaseNotes/22.rst
+++ b/libcxx/docs/ReleaseNotes/22.rst
@@ -57,6 +57,7 @@ Improvements and New Features
has been improved by up to 3x
- The performance of ``insert(iterator, iterator)`` of ``multimap`` and ``multiset`` has been improved by up to 2.5x
- The performance of ``erase(iterator, iterator)`` in the unordered containers has been improved by up to 1.9x
+- The performance of ``map::insert_or_assign`` has been improved by up to 2x
- ``ofstream::write`` has been optimized to pass through large strings to system calls directly instead of copying them
in chunks into a buffer.
diff --git a/libcxx/include/map b/libcxx/include/map
index b53e1c4213487..395b434fe4a5b 100644
--- a/libcxx/include/map
+++ b/libcxx/include/map
@@ -1144,22 +1144,20 @@ public:
template <class _Vp>
_LIBCPP_HIDE_FROM_ABI pair<iterator, bool> insert_or_assign(const key_type& __k, _Vp&& __v) {
- iterator __p = lower_bound(__k);
- if (__p != end() && !key_comp()(__k, __p->first)) {
- __p->second = std::forward<_Vp>(__v);
- return std::make_pair(__p, false);
- }
- return std::make_pair(emplace_hint(__p, __k, std::forward<_Vp>(__v)), true);
+ auto __result = __tree_.__emplace_unique(__k, std::forward<_Vp>(__v));
+ auto& [__iter, __inserted] = __result;
+ if (!__inserted)
+ __iter->second = std::forward<_Vp>(__v);
+ return __result;
}
template <class _Vp>
_LIBCPP_HIDE_FROM_ABI pair<iterator, bool> insert_or_assign(key_type&& __k, _Vp&& __v) {
- iterator __p = lower_bound(__k);
- if (__p != end() && !key_comp()(__k, __p->first)) {
- __p->second = std::forward<_Vp>(__v);
- return std::make_pair(__p, false);
- }
- return std::make_pair(emplace_hint(__p, std::move(__k), std::forward<_Vp>(__v)), true);
+ auto __result = __tree_.__emplace_unique(std::move(__k), std::forward<_Vp>(__v));
+ auto& [__iter, __inserted] = __result;
+ if (!__inserted)
+ __iter->second = std::forward<_Vp>(__v);
+ return __result;
}
template <class _Vp>
diff --git a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
index 535a52f0a08ab..3daa7e1843f5c 100644
--- a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
@@ -259,6 +259,57 @@ void associative_container_benchmarks(std::string container) {
}
});
+ if constexpr (is_map_like && !is_multi_key_container) {
+ bench("insert_or_assign(key, value) (already present)", [=](auto& st) {
+ const std::size_t size = st.range(0) ? st.range(0) : 1;
+ std::vector<Value> in = make_value_types(generate_unique_keys(size));
+ Value to_insert = in[in.size() / 2]; // pick any existing value
+ std::vector<Container> c(BatchSize, Container(in.begin(), in.end()));
+ typename Container::iterator inserted[BatchSize];
+
+ while (st.KeepRunningBatch(BatchSize)) {
+ for (std::size_t i = 0; i != BatchSize; ++i) {
+ inserted[i] =
+ adapt_operations<Container>::get_iterator(c[i].insert_or_assign(to_insert.first, to_insert.second));
+ benchmark::DoNotOptimize(inserted[i]);
+ benchmark::DoNotOptimize(c[i]);
+ benchmark::ClobberMemory();
+ }
+
+ if constexpr (is_multi_key_container) {
+ st.PauseTiming();
+ for (std::size_t i = 0; i != BatchSize; ++i) {
+ c[i].erase(inserted[i]);
+ }
+ st.ResumeTiming();
+ }
+ }
+ });
+
+ bench("insert_or_assign(key, value) (new value)", [=](auto& st) {
+ const std::size_t size = st.range(0);
+ std::vector<Value> in = make_value_types(generate_unique_keys(size + 1));
+ Value to_insert = in.back();
+ in.pop_back();
+ std::vector<Container> c(BatchSize, Container(in.begin(), in.end()));
+
+ while (st.KeepRunningBatch(BatchSize)) {
+ for (std::size_t i = 0; i != BatchSize; ++i) {
+ auto result = c[i].insert_or_assign(to_insert.first, to_insert.second);
+ benchmark::DoNotOptimize(result);
+ benchmark::DoNotOptimize(c[i]);
+ benchmark::ClobberMemory();
+ }
+
+ st.PauseTiming();
+ for (std::size_t i = 0; i != BatchSize; ++i) {
+ c[i].erase(get_key(to_insert));
+ }
+ st.ResumeTiming();
+ }
+ });
+ }
+
// The insert(hint, ...) methods are only relevant for ordered containers, and we lack
// a good way to compute a hint for unordered ones.
if constexpr (is_ordered_container) {
|
ldionne
approved these changes
Aug 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with a minor comment, and also please add a short explanation of the optimization in the commit message. This is great!
61e7688
to
7836c24
Compare
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
__emplace_unique
uses__find_equal
, which can be significantly faster thanlower_bound
. As a nice side-effect, this also changes the implementation to the "naive" implementation of tryinginsert
first, and if that fails assign instead. This also matches theinsert_or_assign
overloads with a hint.