[libcxx-commits] [libcxx] Insert new nodes at the beginning of equal range in std::unordered_multimap (PR #104702)
via libcxx-commits
libcxx-commits at lists.llvm.org
Sun Aug 18 07:54:35 PDT 2024
https://github.com/arvidjonasson updated https://github.com/llvm/llvm-project/pull/104702
>From 7187d6ceffabd089d27e62d1ec6887877b3694ea Mon Sep 17 00:00:00 2001
From: Arvid Jonasson <jonassonarvid02 at gmail.com>
Date: Sun, 18 Aug 2024 14:50:58 +0200
Subject: [PATCH] Insert new nodes at the beginning of equal range in
std::unordered_multimap
- Changed the insertion and emplacement logic in `std::unordered_multimap` to insert new nodes at the beginning of an equal range instead of the back.
- This change addresses a performance issue where inserting at the back degraded performance to that of a linked list for large numbers of elements with equal keys.
- The new approach improves performance significantly, fixing a bug reported in 2013 (Bug 16747 in LLVM Bugzilla) that was previously marked as WONTFIX.
- Note: This change may break code that relies on the order of elements within each equal range, although such order is not guaranteed by the standard.
- This new behavior mimics the insertion logic of the `libstdc++` implementation.
- Added a benchmark to measure the performance improvements of the new insertion logic.
---
libcxx/include/__hash_table | 27 +-
libcxx/test/benchmarks/CMakeLists.txt | 1 +
libcxx/test/benchmarks/ContainerBenchmarks.h | 16 +-
.../unordered_multimap_operations.bench.cpp | 260 ++++++++++++++++++
.../emplace_hint.pass.cpp | 16 --
.../format.functions.tests.h | 2 +-
6 files changed, 287 insertions(+), 35 deletions(-)
create mode 100644 libcxx/test/benchmarks/unordered_multimap_operations.bench.cpp
diff --git a/libcxx/include/__hash_table b/libcxx/include/__hash_table
index d5fbc92a3dfc4e..d4c18599c92509 100644
--- a/libcxx/include/__hash_table
+++ b/libcxx/include/__hash_table
@@ -1398,7 +1398,8 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::__node_insert_unique(__node_pointer __
// Prepare the container for an insertion of the value __cp_val with the hash
// __cp_hash. This does a lookup into the container to see if __cp_value is
// already present, and performs a rehash if necessary. Returns a pointer to the
-// last occurrence of __cp_val in the map.
+// node before the insertion point, or nullptr if the insertion point is the
+// first node in the bucket.
//
// Note that this function does forward exceptions if key_eq() throws, and never
// mutates __value or actually inserts into the map.
@@ -1414,28 +1415,20 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::__node_insert_multi_prepare(size_t __c
size_t __chash = std::__constrain_hash(__cp_hash, __bc);
__next_pointer __pn = __bucket_list_[__chash];
if (__pn != nullptr) {
- for (bool __found = false;
- __pn->__next_ != nullptr && std::__constrain_hash(__pn->__next_->__hash(), __bc) == __chash;
+ for (; __pn->__next_ != nullptr && std::__constrain_hash(__pn->__next_->__hash(), __bc) == __chash;
__pn = __pn->__next_) {
- // __found key_eq() action
- // false false loop
- // true true loop
- // false true set __found to true
- // true false break
- if (__found !=
- (__pn->__next_->__hash() == __cp_hash && key_eq()(__pn->__next_->__upcast()->__get_value(), __cp_val))) {
- if (!__found)
- __found = true;
- else
- break;
- }
+ // Iterate until either:
+ // __pn->__next_ is nullptr, or
+ // __pn->__next_ is in a different bucket, or
+ // __pn->__next_ has the same key as __cp_val
+ if (__pn->__next_->__hash() == __cp_hash && key_eq()(__pn->__next_->__upcast()->__get_value(), __cp_val))
+ break;
}
}
return __pn;
}
-// Insert the node __cp into the container after __pn (which is the last node in
-// the bucket that compares equal to __cp). Rehashing, and checking for
+// Insert the node __cp into the container after __pn. Rehashing, and checking for
// uniqueness has already been performed (in __node_insert_multi_prepare), so
// all we need to do is update the bucket and size(). Assumes that __cp->__hash
// is up-to-date.
diff --git a/libcxx/test/benchmarks/CMakeLists.txt b/libcxx/test/benchmarks/CMakeLists.txt
index 616cf0ff8d2374..3d51a3599a1f8a 100644
--- a/libcxx/test/benchmarks/CMakeLists.txt
+++ b/libcxx/test/benchmarks/CMakeLists.txt
@@ -168,6 +168,7 @@ set(BENCHMARK_TESTS
stringstream.bench.cpp
system_error.bench.cpp
to_chars.bench.cpp
+ unordered_multimap_operations.bench.cpp
unordered_set_operations.bench.cpp
util_smartptr.bench.cpp
variant_visit_1.bench.cpp
diff --git a/libcxx/test/benchmarks/ContainerBenchmarks.h b/libcxx/test/benchmarks/ContainerBenchmarks.h
index 744505b439985b..bcfe8c9f9e0f16 100644
--- a/libcxx/test/benchmarks/ContainerBenchmarks.h
+++ b/libcxx/test/benchmarks/ContainerBenchmarks.h
@@ -99,7 +99,21 @@ void BM_InsertValue(benchmark::State& st, Container c, GenInputs gen) {
while (st.KeepRunning()) {
c.clear();
for (auto it = in.begin(); it != end; ++it) {
- benchmark::DoNotOptimize(&(*c.insert(*it).first));
+ benchmark::DoNotOptimize(c.insert(*it));
+ }
+ benchmark::ClobberMemory();
+ }
+}
+
+template <class Container, class... GenInputs>
+void BM_EmplaceValue(benchmark::State& st, Container c, GenInputs... gen) {
+ auto num = st.range(0);
+ auto args = std::make_tuple(gen(num)...);
+ while (st.KeepRunning()) {
+ c.clear();
+ for (decltype(num) i = 0; i < num; ++i) {
+ benchmark::DoNotOptimize(
+ std::apply([&c, i](auto&&... args) -> decltype(auto) { return c.emplace(args[i]...); }, args));
}
benchmark::ClobberMemory();
}
diff --git a/libcxx/test/benchmarks/unordered_multimap_operations.bench.cpp b/libcxx/test/benchmarks/unordered_multimap_operations.bench.cpp
new file mode 100644
index 00000000000000..0d921896901c0f
--- /dev/null
+++ b/libcxx/test/benchmarks/unordered_multimap_operations.bench.cpp
@@ -0,0 +1,260 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include <cstdint>
+#include <cstdlib>
+#include <functional>
+#include <unordered_map>
+#include <vector>
+#include <utility>
+
+#include "benchmark/benchmark.h"
+
+#include "ContainerBenchmarks.h"
+#include "GenerateInput.h"
+#include "test_macros.h"
+
+using namespace ContainerBenchmarks;
+
+constexpr size_t TestNumInputs = 1024;
+
+template <class GenInputs>
+inline auto getRandomWithMaxCardinality(GenInputs genInputs, size_t maxCardinality) {
+ return [genInputs = std::forward<GenInputs>(genInputs), maxCardinality](size_t N) {
+ auto possibleInputs = genInputs(maxCardinality);
+ decltype(possibleInputs) inputs;
+ size_t minIdx = 0, maxIdx = possibleInputs.size() - 1;
+ for (size_t i = 0; i < N; ++i) {
+ inputs.push_back(possibleInputs[getRandomInteger(minIdx, maxIdx)]);
+ }
+ return inputs;
+ };
+}
+
+template <class GenInputsFirst, class GenInputsSecond>
+inline auto genRandomPairInputs(GenInputsFirst genFirst, GenInputsSecond genSecond) {
+ return [genFirst = std::forward< GenInputsFirst >(genFirst),
+ genSecond = std::forward< GenInputsSecond >(genSecond)](size_t N) {
+ auto first = genFirst(N);
+ auto second = genSecond(N);
+ std::vector<std::pair<typename decltype(first)::value_type, typename decltype(second)::value_type>> inputs;
+ inputs.reserve(N);
+ for (size_t i = 0; i < N; ++i) {
+ inputs.emplace_back(std::move(first[i]), std::move(second[i]));
+ }
+ return inputs;
+ };
+}
+
+//----------------------------------------------------------------------------//
+// BM_InsertValue
+// ---------------------------------------------------------------------------//
+
+BENCHMARK_CAPTURE(BM_InsertValue,
+ unordered_multimap_uint32,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ genRandomPairInputs(getRandomIntegerInputs<uint32_t>, getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_InsertValue,
+ unordered_multimap_uint32_sorted,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ genRandomPairInputs(getSortedIntegerInputs<uint32_t>, getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+// String //
+BENCHMARK_CAPTURE(BM_InsertValue,
+ unordered_multimap_string,
+ std::unordered_multimap<std::string, uint32_t>{},
+ genRandomPairInputs(getRandomStringInputs, getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+// Prefixed String //
+BENCHMARK_CAPTURE(BM_InsertValue,
+ unordered_multimap_prefixed_string,
+ std::unordered_multimap<std::string, uint32_t>{},
+ genRandomPairInputs(getPrefixedRandomStringInputs, getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+// Random with max cardinalities [512, 128, 32, 8, 1] //
+BENCHMARK_CAPTURE(BM_InsertValue,
+ unordered_multimap_uint32_max_cardinality_512,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ genRandomPairInputs(getRandomWithMaxCardinality(getRandomIntegerInputs<uint32_t>, 512),
+ getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_InsertValue,
+ unordered_multimap_uint32_max_cardinality_128,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ genRandomPairInputs(getRandomWithMaxCardinality(getRandomIntegerInputs<uint32_t>, 128),
+ getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_InsertValue,
+ unordered_multimap_uint32_max_cardinality_32,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ genRandomPairInputs(getRandomWithMaxCardinality(getRandomIntegerInputs<uint32_t>, 32),
+ getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_InsertValue,
+ unordered_multimap_uint32_max_cardinality_8,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ genRandomPairInputs(getRandomWithMaxCardinality(getRandomIntegerInputs<uint32_t>, 8),
+ getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_InsertValue,
+ unordered_multimap_uint32_max_cardinality_1,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ genRandomPairInputs(getRandomWithMaxCardinality(getRandomIntegerInputs<uint32_t>, 1),
+ getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(
+ BM_InsertValue,
+ unordered_multimap_string_max_cardinality_512,
+ std::unordered_multimap<std::string, uint32_t>{},
+ genRandomPairInputs(getRandomWithMaxCardinality(getRandomStringInputs, 512), getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(
+ BM_InsertValue,
+ unordered_multimap_string_max_cardinality_128,
+ std::unordered_multimap<std::string, uint32_t>{},
+ genRandomPairInputs(getRandomWithMaxCardinality(getRandomStringInputs, 128), getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(
+ BM_InsertValue,
+ unordered_multimap_string_max_cardinality_32,
+ std::unordered_multimap<std::string, uint32_t>{},
+ genRandomPairInputs(getRandomWithMaxCardinality(getRandomStringInputs, 32), getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(
+ BM_InsertValue,
+ unordered_multimap_string_max_cardinality_8,
+ std::unordered_multimap<std::string, uint32_t>{},
+ genRandomPairInputs(getRandomWithMaxCardinality(getRandomStringInputs, 8), getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(
+ BM_InsertValue,
+ unordered_multimap_string_max_cardinality_1,
+ std::unordered_multimap<std::string, uint32_t>{},
+ genRandomPairInputs(getRandomWithMaxCardinality(getRandomStringInputs, 1), getRandomIntegerInputs<uint32_t>))
+ ->Arg(TestNumInputs);
+
+//----------------------------------------------------------------------------//
+// BM_EmplaceValue
+// ---------------------------------------------------------------------------//
+
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_uint32,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ getRandomIntegerInputs<uint32_t>,
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_uint32_sorted,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ getSortedIntegerInputs<uint32_t>,
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+// String //
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_string,
+ std::unordered_multimap<std::string, uint32_t>{},
+ getRandomStringInputs,
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+// Prefixed String //
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_prefixed_string,
+ std::unordered_multimap<std::string, uint32_t>{},
+ getPrefixedRandomStringInputs,
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+// Random with max cardinalities [512, 128, 32, 8] //
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_uint32_max_cardinality_512,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ getRandomWithMaxCardinality(getRandomIntegerInputs<uint32_t>, 512),
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_uint32_max_cardinality_128,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ getRandomWithMaxCardinality(getRandomIntegerInputs<uint32_t>, 128),
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_uint32_max_cardinality_32,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ getRandomWithMaxCardinality(getRandomIntegerInputs<uint32_t>, 32),
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_uint32_max_cardinality_8,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ getRandomWithMaxCardinality(getRandomIntegerInputs<uint32_t>, 8),
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_uint32_max_cardinality_1,
+ std::unordered_multimap<uint32_t, uint32_t>{},
+ getRandomWithMaxCardinality(getRandomIntegerInputs<uint32_t>, 1),
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_string_max_cardinality_512,
+ std::unordered_multimap<std::string, uint32_t>{},
+ getRandomWithMaxCardinality(getRandomStringInputs, 512),
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_string_max_cardinality_128,
+ std::unordered_multimap<std::string, uint32_t>{},
+ getRandomWithMaxCardinality(getRandomStringInputs, 128),
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_string_max_cardinality_32,
+ std::unordered_multimap<std::string, uint32_t>{},
+ getRandomWithMaxCardinality(getRandomStringInputs, 32),
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_string_max_cardinality_8,
+ std::unordered_multimap<std::string, uint32_t>{},
+ getRandomWithMaxCardinality(getRandomStringInputs, 8),
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+BENCHMARK_CAPTURE(BM_EmplaceValue,
+ unordered_multimap_string_max_cardinality_1,
+ std::unordered_multimap<std::string, uint32_t>{},
+ getRandomWithMaxCardinality(getRandomStringInputs, 1),
+ getRandomIntegerInputs<uint32_t>)
+ ->Arg(TestNumInputs);
+
+BENCHMARK_MAIN();
diff --git a/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.modifiers/emplace_hint.pass.cpp b/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.modifiers/emplace_hint.pass.cpp
index 2a6e3f60ca6b57..ec31c2be1fa747 100644
--- a/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.modifiers/emplace_hint.pass.cpp
+++ b/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.modifiers/emplace_hint.pass.cpp
@@ -43,20 +43,12 @@ int main(int, char**)
assert(c.size() == 2);
assert(r->first == 3);
assert(r->second == Emplaceable(5, 6));
- LIBCPP_ASSERT(r == std::next(c.begin()));
r = c.emplace_hint(r, std::piecewise_construct, std::forward_as_tuple(3),
std::forward_as_tuple(6, 7));
assert(c.size() == 3);
assert(r->first == 3);
assert(r->second == Emplaceable(6, 7));
- LIBCPP_ASSERT(r == std::next(c.begin()));
- r = c.begin();
- assert(r->first == 3);
- LIBCPP_ASSERT(r->second == Emplaceable());
- r = std::next(r, 2);
- assert(r->first == 3);
- LIBCPP_ASSERT(r->second == Emplaceable(5, 6));
}
{
typedef std::unordered_multimap<int, Emplaceable, std::hash<int>, std::equal_to<int>,
@@ -74,20 +66,12 @@ int main(int, char**)
assert(c.size() == 2);
assert(r->first == 3);
assert(r->second == Emplaceable(5, 6));
- LIBCPP_ASSERT(r == std::next(c.begin()));
r = c.emplace_hint(r, std::piecewise_construct, std::forward_as_tuple(3),
std::forward_as_tuple(6, 7));
assert(c.size() == 3);
assert(r->first == 3);
assert(r->second == Emplaceable(6, 7));
- LIBCPP_ASSERT(r == std::next(c.begin()));
- r = c.begin();
- assert(r->first == 3);
- LIBCPP_ASSERT(r->second == Emplaceable());
- r = std::next(r, 2);
- assert(r->first == 3);
- LIBCPP_ASSERT(r->second == Emplaceable(5, 6));
}
return 0;
diff --git a/libcxx/test/std/utilities/format/format.range/format.range.fmtmap/format.functions.tests.h b/libcxx/test/std/utilities/format/format.range/format.range.fmtmap/format.functions.tests.h
index 11088796e53869..326c03d2e5db24 100644
--- a/libcxx/test/std/utilities/format/format.range/format.range.fmtmap/format.functions.tests.h
+++ b/libcxx/test/std/utilities/format/format.range/format.range.fmtmap/format.functions.tests.h
@@ -754,7 +754,7 @@ void test_string(TestFunction check, ExceptionTest check_exception) {
template <class CharT, class TestFunction, class ExceptionTest>
void test_status(TestFunction check, ExceptionTest check_exception) {
- std::unordered_multimap<status, status> input{{status::foobar, status::foo}, {status::foobar, status::bar}};
+ std::multimap<status, status> input{{status::foobar, status::foo}, {status::foobar, status::bar}};
check(SV("{0xaa55: 0xaaaa, 0xaa55: 0x5555}"), SV("{}"), input);
check(SV("{0xaa55: 0xaaaa, 0xaa55: 0x5555}^42"), SV("{}^42"), input);
More information about the libcxx-commits
mailing list