[libcxx-commits] [libcxx] [libc++] Implement generic associative container benchmarks (PR #123663)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Wed Feb 5 13:27:05 PST 2025


https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/123663

>From 8e1b692d1d59ba5f5013d0f6a7b4aa3bacbd346c Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Fri, 17 Jan 2025 10:42:36 -0500
Subject: [PATCH 01/10] [libc++] Implement generic associative container
 benchmarks

This patch implements generic associative container benchmarks
for containers with unique keys. In doing so, it replaces the
existing std::map benchmarks which were based on the cartesian
product infrastructure and were too slow to execute.

These new benchmarks aim to strike a balance between exhaustive
coverage of all operations in the most interesting case, while
executing fairly rapidly (~40s on my machine).
---
 libcxx/test/benchmarks/GenerateInput.h        |   5 +
 .../associative_container_benchmarks.h        | 536 ++++++++++
 .../benchmarks/containers/flat_map.bench.cpp  |  25 +
 .../test/benchmarks/containers/map.bench.cpp  | 940 +-----------------
 .../test/benchmarks/containers/set.bench.cpp  |  23 +
 5 files changed, 597 insertions(+), 932 deletions(-)
 create mode 100644 libcxx/test/benchmarks/containers/associative_container_benchmarks.h
 create mode 100644 libcxx/test/benchmarks/containers/flat_map.bench.cpp
 create mode 100644 libcxx/test/benchmarks/containers/set.bench.cpp

diff --git a/libcxx/test/benchmarks/GenerateInput.h b/libcxx/test/benchmarks/GenerateInput.h
index 081631a32b21d90..c87fd69162e9d43 100644
--- a/libcxx/test/benchmarks/GenerateInput.h
+++ b/libcxx/test/benchmarks/GenerateInput.h
@@ -190,6 +190,7 @@ struct Generate<T> {
   static T arbitrary() { return 42; }
   static T cheap() { return 42; }
   static T expensive() { return 42; }
+  static T random() { return getRandomInteger<T>(std::numeric_limits<T>::min(), std::numeric_limits<T>::max()); }
 };
 
 template <>
@@ -197,6 +198,10 @@ struct Generate<std::string> {
   static std::string arbitrary() { return "hello world"; }
   static std::string cheap() { return "small"; }
   static std::string expensive() { return std::string(256, 'x'); }
+  static std::string random() {
+    auto length = getRandomInteger<std::size_t>(1, 1024);
+    return getRandomString(length);
+  }
 };
 
 #endif // BENCHMARK_GENERATE_INPUT_H
diff --git a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h
new file mode 100644
index 000000000000000..3bddc0c73b54ece
--- /dev/null
+++ b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h
@@ -0,0 +1,536 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_BENCHMARKS_CONTAINERS_ASSOCIATIVE_CONTAINER_BENCHMARKS_H
+#define TEST_BENCHMARKS_CONTAINERS_ASSOCIATIVE_CONTAINER_BENCHMARKS_H
+
+#include <algorithm>
+#include <iterator>
+#include <map>
+#include <flat_map>
+#include <random>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+#include "../GenerateInput.h"
+#include "test_macros.h"
+
+namespace support {
+
+template <class Container>
+struct adapt_operations;
+
+template <class K>
+struct adapt_operations<std::set<K>> {
+  using ValueType = typename std::set<K>::value_type;
+  using KeyType   = typename std::set<K>::key_type;
+  static ValueType value_from_key(KeyType const& k) { return k; }
+  static KeyType key_from_value(ValueType const& value) { return value; }
+};
+
+template <class K, class V>
+struct adapt_operations<std::map<K, V>> {
+  using ValueType = typename std::map<K, V>::value_type;
+  using KeyType   = typename std::map<K, V>::key_type;
+  static ValueType value_from_key(KeyType const& k) { return {k, Generate<V>::arbitrary()}; }
+  static KeyType key_from_value(ValueType const& value) { return value.first; }
+};
+
+#if TEST_STD_VER >= 26
+template <class K, class V>
+struct adapt_operations<std::flat_map<K, V>> {
+  using ValueType = typename std::map<K, V>::value_type;
+  using KeyType   = typename std::map<K, V>::key_type;
+  static ValueType value_from_key(KeyType const& k) { return {k, Generate<V>::arbitrary()}; }
+  static KeyType key_from_value(ValueType const& value) { return value.first; }
+};
+#endif
+
+template <class Container>
+void associative_container_benchmarks(std::string container) {
+  using Key   = typename Container::key_type;
+  using Value = typename Container::value_type;
+
+  auto generate_unique_keys = [=](std::size_t n) {
+    std::set<Key> keys;
+    while (keys.size() < n) {
+      Key k = Generate<Key>::random();
+      keys.insert(k);
+    }
+    return std::vector<Key>(keys.begin(), keys.end());
+  };
+
+  auto add_dummy_mapped_type = [](std::vector<Key> const& keys) {
+    std::vector<Value> kv;
+    for (Key const& k : keys)
+      kv.push_back(adapt_operations<Container>::value_from_key(k));
+    return kv;
+  };
+
+  auto get_key = [](Value const& v) { return adapt_operations<Container>::key_from_value(v); };
+
+  // These benchmarks are structured to perform the operation being benchmarked
+  // a small number of times at each iteration, in order to offset the cost of
+  // PauseTiming() and ResumeTiming().
+  static constexpr std::size_t BatchSize = 10;
+
+  struct ScratchSpace {
+    char storage[sizeof(Container)];
+  };
+
+  /////////////////////////
+  // Constructors
+  /////////////////////////
+  benchmark::RegisterBenchmark(container + "::ctor(const&)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    Container src(in.begin(), in.end());
+    ScratchSpace c[BatchSize];
+
+    while (st.KeepRunningBatch(BatchSize)) {
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        new (c + i) Container(src);
+        benchmark::DoNotOptimize(c + i);
+        benchmark::ClobberMemory();
+      }
+
+      st.PauseTiming();
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        reinterpret_cast<Container*>(c + i)->~Container();
+      }
+      st.ResumeTiming();
+    }
+  })->Arg(1024);
+
+  benchmark::RegisterBenchmark(container + "::ctor(iterator, iterator) (unsorted sequence)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::mt19937 randomness;
+    std::vector<Key> keys = generate_unique_keys(size);
+    std::shuffle(keys.begin(), keys.end(), randomness);
+    std::vector<Value> in = add_dummy_mapped_type(keys);
+    ScratchSpace c[BatchSize];
+
+    while (st.KeepRunningBatch(BatchSize)) {
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        new (c + i) Container(in.begin(), in.end());
+        benchmark::DoNotOptimize(c + i);
+        benchmark::ClobberMemory();
+      }
+
+      st.PauseTiming();
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        reinterpret_cast<Container*>(c + i)->~Container();
+      }
+      st.ResumeTiming();
+    }
+  })->Arg(1024);
+
+  benchmark::RegisterBenchmark(container + "::ctor(iterator, iterator) (sorted sequence)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Key> keys  = generate_unique_keys(size);
+    std::sort(keys.begin(), keys.end());
+    std::vector<Value> in = add_dummy_mapped_type(keys);
+    ScratchSpace c[BatchSize];
+
+    while (st.KeepRunningBatch(BatchSize)) {
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        new (c + i) Container(in.begin(), in.end());
+        benchmark::DoNotOptimize(c + i);
+        benchmark::ClobberMemory();
+      }
+
+      st.PauseTiming();
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        reinterpret_cast<Container*>(c + i)->~Container();
+      }
+      st.ResumeTiming();
+    }
+  })->Arg(1024);
+
+  /////////////////////////
+  // Assignment
+  /////////////////////////
+  benchmark::RegisterBenchmark(container + "::operator=(const&)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    Container src(in.begin(), in.end());
+    Container c[BatchSize];
+
+    while (st.KeepRunningBatch(BatchSize)) {
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        c[i] = src;
+        benchmark::DoNotOptimize(c[i]);
+        benchmark::ClobberMemory();
+      }
+
+      st.PauseTiming();
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        c[i].clear();
+      }
+      st.ResumeTiming();
+    }
+  })->Arg(1024);
+
+  /////////////////////////
+  // Insertion
+  /////////////////////////
+  benchmark::RegisterBenchmark(container + "::insert(value) (already present)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = add_dummy_mapped_type(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()));
+
+    while (st.KeepRunningBatch(BatchSize)) {
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        c[i].insert(to_insert);
+        benchmark::DoNotOptimize(c[i]);
+        benchmark::ClobberMemory();
+      }
+
+      // There is no cleanup to do, since associative containers don't insert
+      // if the key is already present.
+    }
+  })->Arg(1024);
+
+  benchmark::RegisterBenchmark(container + "::insert(value) (new value)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = add_dummy_mapped_type(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) {
+        c[i].insert(to_insert);
+        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();
+    }
+  })->Arg(1024);
+
+  benchmark::RegisterBenchmark(container + "::insert(hint, value) (good hint)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size + 1));
+    Value to_insert        = in.back();
+    in.pop_back();
+
+    std::vector<Container> c(BatchSize, Container(in.begin(), in.end()));
+    typename Container::iterator hints[BatchSize];
+    for (std::size_t i = 0; i != BatchSize; ++i) {
+      hints[i] = c[i].lower_bound(get_key(to_insert));
+    }
+
+    while (st.KeepRunningBatch(BatchSize)) {
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        c[i].insert(hints[i], to_insert);
+        benchmark::DoNotOptimize(c[i]);
+        benchmark::ClobberMemory();
+      }
+
+      st.PauseTiming();
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        c[i].erase(get_key(to_insert));
+        hints[i] = c[i].lower_bound(get_key(to_insert)); // refresh hints in case of invalidation
+      }
+      st.ResumeTiming();
+    }
+  })->Arg(1024);
+
+  benchmark::RegisterBenchmark(container + "::insert(hint, value) (bad hint)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = add_dummy_mapped_type(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) {
+        c[i].insert(c[i].begin(), to_insert);
+        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();
+    }
+  })->Arg(1024);
+
+  benchmark::RegisterBenchmark(container + "::insert(iterator, iterator) (all new keys)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size + (size / 10)));
+
+    // Populate a container with a small number of elements, that's what containers will start with.
+    std::vector<Value> small;
+    for (std::size_t i = 0; i != (size / 10); ++i) {
+      small.push_back(in.back());
+      in.pop_back();
+    }
+    Container c(small.begin(), small.end());
+
+    for (auto _ : st) {
+      c.insert(in.begin(), in.end());
+      benchmark::DoNotOptimize(c);
+      benchmark::ClobberMemory();
+
+      st.PauseTiming();
+      c = Container(small.begin(), small.end());
+      st.ResumeTiming();
+    }
+  })->Arg(1024);
+
+  benchmark::RegisterBenchmark(container + "::insert(iterator, iterator) (half new keys)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+
+    // Populate a container that already contains half the elements we'll try inserting,
+    // that's what our container will start with.
+    std::vector<Value> small;
+    for (std::size_t i = 0; i != size / 2; ++i) {
+      small.push_back(in.at(i * 2));
+    }
+    Container c(small.begin(), small.end());
+
+    for (auto _ : st) {
+      c.insert(in.begin(), in.end());
+      benchmark::DoNotOptimize(c);
+      benchmark::ClobberMemory();
+
+      st.PauseTiming();
+      c = Container(small.begin(), small.end());
+      st.ResumeTiming();
+    }
+  })->Arg(1024);
+
+  /////////////////////////
+  // Erasure
+  /////////////////////////
+  benchmark::RegisterBenchmark(container + "::erase(key) (existent)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    Value element          = in[in.size() / 2]; // pick any element
+    std::vector<Container> c(BatchSize, Container(in.begin(), in.end()));
+
+    while (st.KeepRunningBatch(BatchSize)) {
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        c[i].erase(get_key(element));
+        benchmark::DoNotOptimize(c[i]);
+        benchmark::ClobberMemory();
+      }
+
+      st.PauseTiming();
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        c[i].insert(element);
+      }
+      st.ResumeTiming();
+    }
+  })->Arg(1024);
+
+  benchmark::RegisterBenchmark(container + "::erase(key) (non-existent)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size + 1));
+    Value element          = in.back();
+    in.pop_back();
+    Container c(in.begin(), in.end());
+
+    while (st.KeepRunningBatch(BatchSize)) {
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        c.erase(get_key(element));
+        benchmark::DoNotOptimize(c);
+        benchmark::ClobberMemory();
+      }
+
+      // no cleanup required because we erased a non-existent element
+    }
+  })->Arg(1024);
+
+  benchmark::RegisterBenchmark(container + "::erase(iterator)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    Value element          = in[in.size() / 2]; // pick any element
+
+    std::vector<Container> c;
+    std::vector<typename Container::iterator> iterators;
+    for (std::size_t i = 0; i != BatchSize; ++i) {
+      c.push_back(Container(in.begin(), in.end()));
+      iterators.push_back(c[i].find(get_key(element)));
+    }
+
+    while (st.KeepRunningBatch(BatchSize)) {
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        c[i].erase(iterators[i]);
+        benchmark::DoNotOptimize(c[i]);
+        benchmark::ClobberMemory();
+      }
+
+      st.PauseTiming();
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        iterators[i] = c[i].insert(element).first;
+      }
+      st.ResumeTiming();
+    }
+  })->Arg(1024);
+
+  benchmark::RegisterBenchmark(container + "::erase(iterator, iterator) (erase half the container)", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    Container c(in.begin(), in.end());
+
+    auto first = std::next(c.begin(), c.size() / 4);
+    auto last  = std::next(c.begin(), 3 * (c.size() / 4));
+    for (auto _ : st) {
+      c.erase(first, last);
+      benchmark::DoNotOptimize(c);
+      benchmark::ClobberMemory();
+
+      st.PauseTiming();
+      c     = Container(in.begin(), in.end());
+      first = std::next(c.begin(), c.size() / 4);
+      last  = std::next(c.begin(), 3 * (c.size() / 4));
+      st.ResumeTiming();
+    }
+  })->Arg(1024);
+
+  benchmark::RegisterBenchmark(container + "::clear()", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    Container c(in.begin(), in.end());
+
+    for (auto _ : st) {
+      c.clear();
+      benchmark::DoNotOptimize(c);
+      benchmark::ClobberMemory();
+
+      st.PauseTiming();
+      c = Container(in.begin(), in.end());
+      st.ResumeTiming();
+    }
+  })->Arg(1024);
+
+  /////////////////////////
+  // Query
+  /////////////////////////
+  auto bench_with_existent_key = [=](auto func) {
+    return [=](auto& st) {
+      const std::size_t size = st.range(0);
+      std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+      Value element          = in[in.size() / 2]; // pick any element
+      Container c(in.begin(), in.end());
+
+      while (st.KeepRunningBatch(BatchSize)) {
+        for (std::size_t i = 0; i != BatchSize; ++i) {
+          auto result = func(c, element);
+          benchmark::DoNotOptimize(c);
+          benchmark::DoNotOptimize(result);
+          benchmark::ClobberMemory();
+        }
+      }
+    };
+  };
+
+  auto bench_with_nonexistent_key = [=](auto func) {
+    return [=](auto& st) {
+      const std::size_t size = st.range(0);
+      std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size + 1));
+      Value element          = in.back();
+      in.pop_back();
+      Container c(in.begin(), in.end());
+
+      while (st.KeepRunningBatch(BatchSize)) {
+        for (std::size_t i = 0; i != BatchSize; ++i) {
+          auto result = func(c, element);
+          benchmark::DoNotOptimize(c);
+          benchmark::DoNotOptimize(result);
+          benchmark::ClobberMemory();
+        }
+      }
+    };
+  };
+
+  benchmark::RegisterBenchmark(
+      container + "::find(key) (existent)",
+      bench_with_existent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); }))
+      ->Arg(1024);
+  benchmark::RegisterBenchmark(
+      container + "::find(key) (non-existent)",
+      bench_with_nonexistent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); }))
+      ->Arg(1024);
+
+  benchmark::RegisterBenchmark(
+      container + "::count(key) (existent)",
+      bench_with_existent_key([=](Container const& c, Value const& element) { return c.count(get_key(element)); }))
+      ->Arg(1024);
+  benchmark::RegisterBenchmark(
+      container + "::count(key) (non-existent)",
+      bench_with_nonexistent_key([=](Container const& c, Value const& element) { return c.count(get_key(element)); }))
+      ->Arg(1024);
+
+  benchmark::RegisterBenchmark(
+      container + "::contains(key) (existent)",
+      bench_with_existent_key([=](Container const& c, Value const& element) { return c.contains(get_key(element)); }))
+      ->Arg(1024);
+  benchmark::RegisterBenchmark(
+      container + "::contains(key) (non-existent)",
+      bench_with_nonexistent_key([=](Container const& c, Value const& element) {
+        return c.contains(get_key(element));
+      }))
+      ->Arg(1024);
+
+  benchmark::RegisterBenchmark(
+      container + "::lower_bound(key) (existent)",
+      bench_with_existent_key([=](Container const& c, Value const& element) {
+        return c.lower_bound(get_key(element));
+      }))
+      ->Arg(1024);
+  benchmark::RegisterBenchmark(
+      container + "::lower_bound(key) (non-existent)",
+      bench_with_nonexistent_key([=](Container const& c, Value const& element) {
+        return c.lower_bound(get_key(element));
+      }))
+      ->Arg(1024);
+
+  benchmark::RegisterBenchmark(
+      container + "::upper_bound(key) (existent)",
+      bench_with_existent_key([=](Container const& c, Value const& element) {
+        return c.upper_bound(get_key(element));
+      }))
+      ->Arg(1024);
+  benchmark::RegisterBenchmark(
+      container + "::upper_bound(key) (non-existent)",
+      bench_with_nonexistent_key([=](Container const& c, Value const& element) {
+        return c.upper_bound(get_key(element));
+      }))
+      ->Arg(1024);
+
+  benchmark::RegisterBenchmark(
+      container + "::equal_range(key) (existent)",
+      bench_with_existent_key([=](Container const& c, Value const& element) {
+        return c.equal_range(get_key(element));
+      }))
+      ->Arg(1024);
+  benchmark::RegisterBenchmark(
+      container + "::equal_range(key) (non-existent)",
+      bench_with_nonexistent_key([=](Container const& c, Value const& element) {
+        return c.equal_range(get_key(element));
+      }))
+      ->Arg(1024);
+}
+
+} // namespace support
+
+#endif // TEST_BENCHMARKS_CONTAINERS_ASSOCIATIVE_CONTAINER_BENCHMARKS_H
diff --git a/libcxx/test/benchmarks/containers/flat_map.bench.cpp b/libcxx/test/benchmarks/containers/flat_map.bench.cpp
new file mode 100644
index 000000000000000..39971c9319e6c5d
--- /dev/null
+++ b/libcxx/test/benchmarks/containers/flat_map.bench.cpp
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+#include <flat_map>
+#include <string>
+
+#include "associative_container_benchmarks.h"
+#include "benchmark/benchmark.h"
+
+int main(int argc, char** argv) {
+  support::associative_container_benchmarks<std::flat_map<int, int>>("std::flat_map<int, int>");
+  support::associative_container_benchmarks<std::flat_map<std::string, int>>("std::flat_map<std::string, int>");
+
+  benchmark::Initialize(&argc, argv);
+  benchmark::RunSpecifiedBenchmarks();
+  benchmark::Shutdown();
+  return 0;
+}
diff --git a/libcxx/test/benchmarks/containers/map.bench.cpp b/libcxx/test/benchmarks/containers/map.bench.cpp
index e37c7d8d5516350..5c5ba7cc9d3db01 100644
--- a/libcxx/test/benchmarks/containers/map.bench.cpp
+++ b/libcxx/test/benchmarks/containers/map.bench.cpp
@@ -6,944 +6,20 @@
 //
 //===----------------------------------------------------------------------===//
 
-// UNSUPPORTED: c++03, c++11, c++14
+// UNSUPPORTED: c++03, c++11, c++14, c++17
 
-#include <algorithm>
-#include <cstdint>
 #include <map>
-#include <random>
-#include <vector>
+#include <string>
 
-#include "../CartesianBenchmarks.h"
+#include "associative_container_benchmarks.h"
 #include "benchmark/benchmark.h"
-#include "test_macros.h"
-
-// When VALIDATE is defined the benchmark will run to validate the benchmarks.
-// The time taken by several operations depend on whether or not an element
-// exists. To avoid errors in the benchmark these operations have a validation
-// mode to test the benchmark. Since they are not meant to be benchmarked the
-// number of sizes tested is limited to 1.
-// #define VALIDATE
-
-namespace {
-
-enum class Mode { Hit, Miss };
-
-struct AllModes : EnumValuesAsTuple<AllModes, Mode, 2> {
-  static constexpr const char* Names[] = {"ExistingElement", "NewElement"};
-};
-
-// The positions of the hints to pick:
-// - Begin picks the first item. The item cannot be put before this element.
-// - Thrid picks the third item. This is just an element with a valid entry
-//   before and after it.
-// - Correct contains the correct hint.
-// - End contains a hint to the end of the map.
-enum class Hint { Begin, Third, Correct, End };
-struct AllHints : EnumValuesAsTuple<AllHints, Hint, 4> {
-  static constexpr const char* Names[] = {"Begin", "Third", "Correct", "End"};
-};
-
-enum class Order { Sorted, Random };
-struct AllOrders : EnumValuesAsTuple<AllOrders, Order, 2> {
-  static constexpr const char* Names[] = {"Sorted", "Random"};
-};
-
-struct TestSets {
-  std::vector<uint64_t> Keys;
-  std::vector<std::map<uint64_t, int64_t> > Maps;
-  std::vector<std::vector<typename std::map<uint64_t, int64_t>::const_iterator> > Hints;
-};
-
-enum class Shuffle { None, Keys, Hints };
-
-TestSets makeTestingSets(size_t MapSize, Mode mode, Shuffle shuffle, size_t max_maps) {
-  /*
-   * The shuffle does not retain the random number generator to use the same
-   * set of random numbers for every iteration.
-   */
-  TestSets R;
-
-  int MapCount = std::min(max_maps, 1000000 / MapSize);
-
-  for (uint64_t I = 0; I < MapSize; ++I) {
-    R.Keys.push_back(mode == Mode::Hit ? 2 * I + 2 : 2 * I + 1);
-  }
-  if (shuffle == Shuffle::Keys)
-    std::shuffle(R.Keys.begin(), R.Keys.end(), std::mt19937());
-
-  for (int M = 0; M < MapCount; ++M) {
-    auto& map   = R.Maps.emplace_back();
-    auto& hints = R.Hints.emplace_back();
-    for (uint64_t I = 0; I < MapSize; ++I) {
-      hints.push_back(map.insert(std::make_pair(2 * I + 2, 0)).first);
-    }
-    if (shuffle == Shuffle::Hints)
-      std::shuffle(hints.begin(), hints.end(), std::mt19937());
-  }
-
-  return R;
-}
-
-struct Base {
-  size_t MapSize;
-  Base(size_t T) : MapSize(T) {}
-
-  std::string baseName() const { return "_MapSize=" + std::to_string(MapSize); }
-};
-
-//*******************************************************************|
-//                       Member functions                            |
-//*******************************************************************|
-
-struct ConstructorDefault {
-  void run(benchmark::State& State) const {
-    for (auto _ : State) {
-      benchmark::DoNotOptimize(std::map<uint64_t, int64_t>());
-    }
-  }
-
-  std::string name() const { return "BM_ConstructorDefault"; }
-};
-
-struct ConstructorIterator : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1);
-    auto& Map = Data.Maps.front();
-    while (State.KeepRunningBatch(MapSize)) {
-#ifndef VALIDATE
-      benchmark::DoNotOptimize(std::map<uint64_t, int64_t>(Map.begin(), Map.end()));
-#else
-      std::map<uint64_t, int64_t> M{Map.begin(), Map.end()};
-      if (M != Map)
-        State.SkipWithError("Map copy not identical");
-#endif
-    }
-  }
-
-  std::string name() const { return "BM_ConstructorIterator" + baseName(); }
-};
-
-struct ConstructorCopy : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1);
-    auto& Map = Data.Maps.front();
-    while (State.KeepRunningBatch(MapSize)) {
-#ifndef VALIDATE
-      std::map<uint64_t, int64_t> M(Map);
-      benchmark::DoNotOptimize(M);
-#else
-      std::map<uint64_t, int64_t> M(Map);
-      if (M != Map)
-        State.SkipWithError("Map copy not identical");
-#endif
-    }
-  }
-
-  std::string name() const { return "BM_ConstructorCopy" + baseName(); }
-};
-
-struct ConstructorMove : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (auto& Map : Data.Maps) {
-        std::map<uint64_t, int64_t> M(std::move(Map));
-        benchmark::DoNotOptimize(M);
-      }
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  std::string name() const { return "BM_ConstructorMove" + baseName(); }
-};
-
-//*******************************************************************|
-//                           Capacity                                |
-//*******************************************************************|
-
-struct Empty : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1);
-    auto& Map = Data.Maps.front();
-    for (auto _ : State) {
-#ifndef VALIDATE
-      benchmark::DoNotOptimize(Map.empty());
-#else
-      if (Map.empty())
-        State.SkipWithError("Map contains an invalid number of elements.");
-#endif
-    }
-  }
-
-  std::string name() const { return "BM_Empty" + baseName(); }
-};
-
-struct Size : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1);
-    auto& Map = Data.Maps.front();
-    for (auto _ : State) {
-#ifndef VALIDATE
-      benchmark::DoNotOptimize(Map.size());
-#else
-      if (Map.size() != MapSize)
-        State.SkipWithError("Map contains an invalid number of elements.");
-#endif
-    }
-  }
-
-  std::string name() const { return "BM_Size" + baseName(); }
-};
-
-//*******************************************************************|
-//                           Modifiers                               |
-//*******************************************************************|
-
-struct Clear : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (auto& Map : Data.Maps) {
-        Map.clear();
-        benchmark::DoNotOptimize(Map);
-      }
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  std::string name() const { return "BM_Clear" + baseName(); }
-};
-
-template <class Mode, class Order>
-struct Insert : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (auto& Map : Data.Maps) {
-        for (auto K : Data.Keys) {
-#ifndef VALIDATE
-          benchmark::DoNotOptimize(Map.insert(std::make_pair(K, 1)));
-#else
-          bool Inserted = Map.insert(std::make_pair(K, 1)).second;
-          if (Mode() == ::Mode::Hit) {
-            if (Inserted)
-              State.SkipWithError("Inserted a duplicate element");
-          } else {
-            if (!Inserted)
-              State.SkipWithError("Failed to insert e new element");
-          }
-#endif
-        }
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  std::string name() const { return "BM_Insert" + baseName() + Mode::name() + Order::name(); }
-};
-
-template <class Mode, class Hint>
-struct InsertHint : Base {
-  using Base::Base;
-
-  template < ::Hint hint>
-  typename std::enable_if<hint == ::Hint::Correct>::type run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (size_t I = 0; I < Data.Maps.size(); ++I) {
-        auto& Map = Data.Maps[I];
-        auto H    = Data.Hints[I].begin();
-        for (auto K : Data.Keys) {
-#ifndef VALIDATE
-          benchmark::DoNotOptimize(Map.insert(*H, std::make_pair(K, 1)));
-#else
-          auto Inserted = Map.insert(*H, std::make_pair(K, 1));
-          if (Mode() == ::Mode::Hit) {
-            if (Inserted != *H)
-              State.SkipWithError("Inserted a duplicate element");
-          } else {
-            if (++Inserted != *H)
-              State.SkipWithError("Failed to insert a new element");
-          }
-#endif
-          ++H;
-        }
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  template < ::Hint hint>
-  typename std::enable_if<hint != ::Hint::Correct>::type run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (size_t I = 0; I < Data.Maps.size(); ++I) {
-        auto& Map  = Data.Maps[I];
-        auto Third = *(Data.Hints[I].begin() + 2);
-        for (auto K : Data.Keys) {
-          auto Itor = hint == ::Hint::Begin ? Map.begin() : hint == ::Hint::Third ? Third : Map.end();
-#ifndef VALIDATE
-          benchmark::DoNotOptimize(Map.insert(Itor, std::make_pair(K, 1)));
-#else
-          size_t Size = Map.size();
-          Map.insert(Itor, std::make_pair(K, 1));
-          if (Mode() == ::Mode::Hit) {
-            if (Size != Map.size())
-              State.SkipWithError("Inserted a duplicate element");
-          } else {
-            if (Size + 1 != Map.size())
-              State.SkipWithError("Failed to insert a new element");
-          }
-#endif
-        }
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  void run(benchmark::State& State) const {
-    static constexpr auto h = Hint();
-    run<h>(State);
-  }
-
-  std::string name() const { return "BM_InsertHint" + baseName() + Mode::name() + Hint::name(); }
-};
-
-template <class Mode, class Order>
-struct InsertAssign : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (auto& Map : Data.Maps) {
-        for (auto K : Data.Keys) {
-#ifndef VALIDATE
-          benchmark::DoNotOptimize(Map.insert_or_assign(K, 1));
-#else
-          bool Inserted = Map.insert_or_assign(K, 1).second;
-          if (Mode() == ::Mode::Hit) {
-            if (Inserted)
-              State.SkipWithError("Inserted a duplicate element");
-          } else {
-            if (!Inserted)
-              State.SkipWithError("Failed to insert e new element");
-          }
-#endif
-        }
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  std::string name() const { return "BM_InsertAssign" + baseName() + Mode::name() + Order::name(); }
-};
-
-template <class Mode, class Hint>
-struct InsertAssignHint : Base {
-  using Base::Base;
-
-  template < ::Hint hint>
-  typename std::enable_if<hint == ::Hint::Correct>::type run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (size_t I = 0; I < Data.Maps.size(); ++I) {
-        auto& Map = Data.Maps[I];
-        auto H    = Data.Hints[I].begin();
-        for (auto K : Data.Keys) {
-#ifndef VALIDATE
-          benchmark::DoNotOptimize(Map.insert_or_assign(*H, K, 1));
-#else
-          auto Inserted = Map.insert_or_assign(*H, K, 1);
-          if (Mode() == ::Mode::Hit) {
-            if (Inserted != *H)
-              State.SkipWithError("Inserted a duplicate element");
-          } else {
-            if (++Inserted != *H)
-              State.SkipWithError("Failed to insert a new element");
-          }
-#endif
-          ++H;
-        }
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  template < ::Hint hint>
-  typename std::enable_if<hint != ::Hint::Correct>::type run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (size_t I = 0; I < Data.Maps.size(); ++I) {
-        auto& Map  = Data.Maps[I];
-        auto Third = *(Data.Hints[I].begin() + 2);
-        for (auto K : Data.Keys) {
-          auto Itor = hint == ::Hint::Begin ? Map.begin() : hint == ::Hint::Third ? Third : Map.end();
-#ifndef VALIDATE
-          benchmark::DoNotOptimize(Map.insert_or_assign(Itor, K, 1));
-#else
-          size_t Size = Map.size();
-          Map.insert_or_assign(Itor, K, 1);
-          if (Mode() == ::Mode::Hit) {
-            if (Size != Map.size())
-              State.SkipWithError("Inserted a duplicate element");
-          } else {
-            if (Size + 1 != Map.size())
-              State.SkipWithError("Failed to insert a new element");
-          }
-#endif
-        }
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  void run(benchmark::State& State) const {
-    static constexpr auto h = Hint();
-    run<h>(State);
-  }
-
-  std::string name() const { return "BM_InsertAssignHint" + baseName() + Mode::name() + Hint::name(); }
-};
-
-template <class Mode, class Order>
-struct Emplace : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (auto& Map : Data.Maps) {
-        for (auto K : Data.Keys) {
-#ifndef VALIDATE
-          benchmark::DoNotOptimize(Map.emplace(K, 1));
-#else
-          bool Inserted = Map.emplace(K, 1).second;
-          if (Mode() == ::Mode::Hit) {
-            if (Inserted)
-              State.SkipWithError("Emplaced a duplicate element");
-          } else {
-            if (!Inserted)
-              State.SkipWithError("Failed to emplace a new element");
-          }
-#endif
-        }
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  std::string name() const { return "BM_Emplace" + baseName() + Mode::name() + Order::name(); }
-};
-
-template <class Mode, class Hint>
-struct EmplaceHint : Base {
-  using Base::Base;
-
-  template < ::Hint hint>
-  typename std::enable_if<hint == ::Hint::Correct>::type run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (size_t I = 0; I < Data.Maps.size(); ++I) {
-        auto& Map = Data.Maps[I];
-        auto H    = Data.Hints[I].begin();
-        for (auto K : Data.Keys) {
-#ifndef VALIDATE
-          benchmark::DoNotOptimize(Map.emplace_hint(*H, K, 1));
-#else
-          auto Inserted = Map.emplace_hint(*H, K, 1);
-          if (Mode() == ::Mode::Hit) {
-            if (Inserted != *H)
-              State.SkipWithError("Emplaced a duplicate element");
-          } else {
-            if (++Inserted != *H)
-              State.SkipWithError("Failed to emplace a new element");
-          }
-#endif
-          ++H;
-        }
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  template < ::Hint hint>
-  typename std::enable_if<hint != ::Hint::Correct>::type run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (size_t I = 0; I < Data.Maps.size(); ++I) {
-        auto& Map  = Data.Maps[I];
-        auto Third = *(Data.Hints[I].begin() + 2);
-        for (auto K : Data.Keys) {
-          auto Itor = hint == ::Hint::Begin ? Map.begin() : hint == ::Hint::Third ? Third : Map.end();
-#ifndef VALIDATE
-          benchmark::DoNotOptimize(Map.emplace_hint(Itor, K, 1));
-#else
-          size_t Size = Map.size();
-          Map.emplace_hint(Itor, K, 1);
-          if (Mode() == ::Mode::Hit) {
-            if (Size != Map.size())
-              State.SkipWithError("Emplaced a duplicate element");
-          } else {
-            if (Size + 1 != Map.size())
-              State.SkipWithError("Failed to emplace a new element");
-          }
-#endif
-        }
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  void run(benchmark::State& State) const {
-    static constexpr auto h = Hint();
-    run<h>(State);
-  }
-
-  std::string name() const { return "BM_EmplaceHint" + baseName() + Mode::name() + Hint::name(); }
-};
-
-template <class Mode, class Order>
-struct TryEmplace : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (auto& Map : Data.Maps) {
-        for (auto K : Data.Keys) {
-#ifndef VALIDATE
-          benchmark::DoNotOptimize(Map.try_emplace(K, 1));
-#else
-          bool Inserted = Map.try_emplace(K, 1).second;
-          if (Mode() == ::Mode::Hit) {
-            if (Inserted)
-              State.SkipWithError("Emplaced a duplicate element");
-          } else {
-            if (!Inserted)
-              State.SkipWithError("Failed to emplace a new element");
-          }
-#endif
-        }
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  std::string name() const { return "BM_TryEmplace" + baseName() + Mode::name() + Order::name(); }
-};
-
-template <class Mode, class Hint>
-struct TryEmplaceHint : Base {
-  using Base::Base;
-
-  template < ::Hint hint>
-  typename std::enable_if<hint == ::Hint::Correct>::type run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (size_t I = 0; I < Data.Maps.size(); ++I) {
-        auto& Map = Data.Maps[I];
-        auto H    = Data.Hints[I].begin();
-        for (auto K : Data.Keys) {
-#ifndef VALIDATE
-          benchmark::DoNotOptimize(Map.try_emplace(*H, K, 1));
-#else
-          auto Inserted = Map.try_emplace(*H, K, 1);
-          if (Mode() == ::Mode::Hit) {
-            if (Inserted != *H)
-              State.SkipWithError("Emplaced a duplicate element");
-          } else {
-            if (++Inserted != *H)
-              State.SkipWithError("Failed to emplace a new element");
-          }
-#endif
-          ++H;
-        }
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  template < ::Hint hint>
-  typename std::enable_if<hint != ::Hint::Correct>::type run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (size_t I = 0; I < Data.Maps.size(); ++I) {
-        auto& Map  = Data.Maps[I];
-        auto Third = *(Data.Hints[I].begin() + 2);
-        for (auto K : Data.Keys) {
-          auto Itor = hint == ::Hint::Begin ? Map.begin() : hint == ::Hint::Third ? Third : Map.end();
-#ifndef VALIDATE
-          benchmark::DoNotOptimize(Map.try_emplace(Itor, K, 1));
-#else
-          size_t Size = Map.size();
-          Map.try_emplace(Itor, K, 1);
-          if (Mode() == ::Mode::Hit) {
-            if (Size != Map.size())
-              State.SkipWithError("Emplaced a duplicate element");
-          } else {
-            if (Size + 1 != Map.size())
-              State.SkipWithError("Failed to emplace a new element");
-          }
-#endif
-        }
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  void run(benchmark::State& State) const {
-    static constexpr auto h = Hint();
-    run<h>(State);
-  }
-
-  std::string name() const { return "BM_TryEmplaceHint" + baseName() + Mode::name() + Hint::name(); }
-};
-
-template <class Mode, class Order>
-struct Erase : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (auto& Map : Data.Maps) {
-        for (auto K : Data.Keys) {
-#ifndef VALIDATE
-          benchmark::DoNotOptimize(Map.erase(K));
-#else
-          size_t I = Map.erase(K);
-          if (Mode() == ::Mode::Hit) {
-            if (I == 0)
-              State.SkipWithError("Did not find the existing element");
-          } else {
-            if (I == 1)
-              State.SkipWithError("Did find the non-existing element");
-          }
-#endif
-        }
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  std::string name() const { return "BM_Erase" + baseName() + Mode::name() + Order::name(); }
-};
-
-template <class Order>
-struct EraseIterator : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data =
-        makeTestingSets(MapSize, Mode::Hit, Order::value == ::Order::Random ? Shuffle::Hints : Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (size_t I = 0; I < Data.Maps.size(); ++I) {
-        auto& Map = Data.Maps[I];
-        for (auto H : Data.Hints[I]) {
-          benchmark::DoNotOptimize(Map.erase(H));
-        }
-#ifdef VALIDATE
-        if (!Map.empty())
-          State.SkipWithError("Did not erase the entire map");
-#endif
-      }
-
-      State.PauseTiming();
-      Data =
-          makeTestingSets(MapSize, Mode::Hit, Order::value == ::Order::Random ? Shuffle::Hints : Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  std::string name() const { return "BM_EraseIterator" + baseName() + Order::name(); }
-};
-
-struct EraseRange : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1000);
-    while (State.KeepRunningBatch(MapSize * Data.Maps.size())) {
-      for (auto& Map : Data.Maps) {
-#ifndef VALIDATE
-        benchmark::DoNotOptimize(Map.erase(Map.begin(), Map.end()));
-#else
-        Map.erase(Map.begin(), Map.end());
-        if (!Map.empty())
-          State.SkipWithError("Did not erase the entire map");
-#endif
-      }
-
-      State.PauseTiming();
-      Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1000);
-      State.ResumeTiming();
-    }
-  }
-
-  std::string name() const { return "BM_EraseRange" + baseName(); }
-};
-
-//*******************************************************************|
-//                            Lookup                                 |
-//*******************************************************************|
-
-template <class Mode, class Order>
-struct Count : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1);
-    auto& Map = Data.Maps.front();
-    while (State.KeepRunningBatch(MapSize)) {
-      for (auto K : Data.Keys) {
-#ifndef VALIDATE
-        benchmark::DoNotOptimize(Map.count(K));
-#else
-        size_t I = Map.count(K);
-        if (Mode() == ::Mode::Hit) {
-          if (I == 0)
-            State.SkipWithError("Did not find the existing element");
-        } else {
-          if (I == 1)
-            State.SkipWithError("Did find the non-existing element");
-        }
-#endif
-      }
-    }
-  }
-
-  std::string name() const { return "BM_Count" + baseName() + Mode::name() + Order::name(); }
-};
-
-template <class Mode, class Order>
-struct Find : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1);
-    auto& Map = Data.Maps.front();
-    while (State.KeepRunningBatch(MapSize)) {
-      for (auto K : Data.Keys) {
-#ifndef VALIDATE
-        benchmark::DoNotOptimize(Map.find(K));
-#else
-        auto Itor = Map.find(K);
-        if (Mode() == ::Mode::Hit) {
-          if (Itor == Map.end())
-            State.SkipWithError("Did not find the existing element");
-        } else {
-          if (Itor != Map.end())
-            State.SkipWithError("Did find the non-existing element");
-        }
-#endif
-      }
-    }
-  }
-
-  std::string name() const { return "BM_Find" + baseName() + Mode::name() + Order::name(); }
-};
-
-template <class Mode, class Order>
-struct EqualRange : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1);
-    auto& Map = Data.Maps.front();
-    while (State.KeepRunningBatch(MapSize)) {
-      for (auto K : Data.Keys) {
-#ifndef VALIDATE
-        benchmark::DoNotOptimize(Map.equal_range(K));
-#else
-        auto Range = Map.equal_range(K);
-        if (Mode() == ::Mode::Hit) {
-          // Adjust validation for the last element.
-          auto Key = K;
-          if (Range.second == Map.end() && K == 2 * MapSize) {
-            --Range.second;
-            Key -= 2;
-          }
-          if (Range.first == Map.end() || Range.first->first != K || Range.second == Map.end() ||
-              Range.second->first - 2 != Key)
-            State.SkipWithError("Did not find the existing element");
-        } else {
-          if (Range.first == Map.end() || Range.first->first - 1 != K || Range.second == Map.end() ||
-              Range.second->first - 1 != K)
-            State.SkipWithError("Did find the non-existing element");
-        }
-#endif
-      }
-    }
-  }
-
-  std::string name() const { return "BM_EqualRange" + baseName() + Mode::name() + Order::name(); }
-};
-
-template <class Mode, class Order>
-struct LowerBound : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1);
-    auto& Map = Data.Maps.front();
-    while (State.KeepRunningBatch(MapSize)) {
-      for (auto K : Data.Keys) {
-#ifndef VALIDATE
-        benchmark::DoNotOptimize(Map.lower_bound(K));
-#else
-        auto Itor = Map.lower_bound(K);
-        if (Mode() == ::Mode::Hit) {
-          if (Itor == Map.end() || Itor->first != K)
-            State.SkipWithError("Did not find the existing element");
-        } else {
-          if (Itor == Map.end() || Itor->first - 1 != K)
-            State.SkipWithError("Did find the non-existing element");
-        }
-#endif
-      }
-    }
-  }
-
-  std::string name() const { return "BM_LowerBound" + baseName() + Mode::name() + Order::name(); }
-};
-
-template <class Mode, class Order>
-struct UpperBound : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1);
-    auto& Map = Data.Maps.front();
-    while (State.KeepRunningBatch(MapSize)) {
-      for (auto K : Data.Keys) {
-#ifndef VALIDATE
-        benchmark::DoNotOptimize(Map.upper_bound(K));
-#else
-        std::map<uint64_t, int64_t>::iterator Itor = Map.upper_bound(K);
-        if (Mode() == ::Mode::Hit) {
-          // Adjust validation for the last element.
-          auto Key = K;
-          if (Itor == Map.end() && K == 2 * MapSize) {
-            --Itor;
-            Key -= 2;
-          }
-          if (Itor == Map.end() || Itor->first - 2 != Key)
-            State.SkipWithError("Did not find the existing element");
-        } else {
-          if (Itor == Map.end() || Itor->first - 1 != K)
-            State.SkipWithError("Did find the non-existing element");
-        }
-#endif
-      }
-    }
-  }
-
-  std::string name() const { return "BM_UpperBound" + baseName() + Mode::name() + Order::name(); }
-};
-
-} // namespace
 
 int main(int argc, char** argv) {
-  benchmark::Initialize(&argc, argv);
-  if (benchmark::ReportUnrecognizedArguments(argc, argv))
-    return 1;
-
-#ifdef VALIDATE
-  const std::vector<size_t> MapSize{10};
-#else
-  const std::vector<size_t> MapSize{10, 100, 1000, 10000, 100000, 1000000};
-#endif
-
-  // Member functions
-  makeCartesianProductBenchmark<ConstructorDefault>();
-  makeCartesianProductBenchmark<ConstructorIterator>(MapSize);
-  makeCartesianProductBenchmark<ConstructorCopy>(MapSize);
-  makeCartesianProductBenchmark<ConstructorMove>(MapSize);
-
-  // Capacity
-  makeCartesianProductBenchmark<Empty>(MapSize);
-  makeCartesianProductBenchmark<Size>(MapSize);
-
-  // Modifiers
-  makeCartesianProductBenchmark<Clear>(MapSize);
-  makeCartesianProductBenchmark<Insert, AllModes, AllOrders>(MapSize);
-  makeCartesianProductBenchmark<InsertHint, AllModes, AllHints>(MapSize);
-  makeCartesianProductBenchmark<InsertAssign, AllModes, AllOrders>(MapSize);
-  makeCartesianProductBenchmark<InsertAssignHint, AllModes, AllHints>(MapSize);
-
-  makeCartesianProductBenchmark<Emplace, AllModes, AllOrders>(MapSize);
-  makeCartesianProductBenchmark<EmplaceHint, AllModes, AllHints>(MapSize);
-  makeCartesianProductBenchmark<TryEmplace, AllModes, AllOrders>(MapSize);
-  makeCartesianProductBenchmark<TryEmplaceHint, AllModes, AllHints>(MapSize);
-  makeCartesianProductBenchmark<Erase, AllModes, AllOrders>(MapSize);
-  makeCartesianProductBenchmark<EraseIterator, AllOrders>(MapSize);
-  makeCartesianProductBenchmark<EraseRange>(MapSize);
-
-  // Lookup
-  makeCartesianProductBenchmark<Count, AllModes, AllOrders>(MapSize);
-  makeCartesianProductBenchmark<Find, AllModes, AllOrders>(MapSize);
-  makeCartesianProductBenchmark<EqualRange, AllModes, AllOrders>(MapSize);
-  makeCartesianProductBenchmark<LowerBound, AllModes, AllOrders>(MapSize);
-  makeCartesianProductBenchmark<UpperBound, AllModes, AllOrders>(MapSize);
+  support::associative_container_benchmarks<std::map<int, int>>("std::map<int, int>");
+  support::associative_container_benchmarks<std::map<std::string, int>>("std::map<std::string, int>");
 
+  benchmark::Initialize(&argc, argv);
   benchmark::RunSpecifiedBenchmarks();
+  benchmark::Shutdown();
+  return 0;
 }
diff --git a/libcxx/test/benchmarks/containers/set.bench.cpp b/libcxx/test/benchmarks/containers/set.bench.cpp
new file mode 100644
index 000000000000000..6a8de0862f2ae3c
--- /dev/null
+++ b/libcxx/test/benchmarks/containers/set.bench.cpp
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+#include <set>
+
+#include "associative_container_benchmarks.h"
+#include "benchmark/benchmark.h"
+
+int main(int argc, char** argv) {
+  support::associative_container_benchmarks<std::set<int>>("std::set<int>");
+
+  benchmark::Initialize(&argc, argv);
+  benchmark::RunSpecifiedBenchmarks();
+  benchmark::Shutdown();
+  return 0;
+}

>From c0cf8868578eb32d56ee843505815485e7e33309 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 5 Feb 2025 14:31:16 -0500
Subject: [PATCH 02/10] Alignment of buffer

---
 .../benchmarks/containers/associative_container_benchmarks.h    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h
index 3bddc0c73b54ece..18dcc3c110770f8 100644
--- a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h
@@ -81,7 +81,7 @@ void associative_container_benchmarks(std::string container) {
   // PauseTiming() and ResumeTiming().
   static constexpr std::size_t BatchSize = 10;
 
-  struct ScratchSpace {
+  struct alignas(Container) ScratchSpace {
     char storage[sizeof(Container)];
   };
 

>From b09ebaba3e5b2cc035ddec38d293d69da322ebb8 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 5 Feb 2025 14:31:26 -0500
Subject: [PATCH 03/10] Use std-at-least-c++26

---
 libcxx/test/benchmarks/containers/flat_map.bench.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/test/benchmarks/containers/flat_map.bench.cpp b/libcxx/test/benchmarks/containers/flat_map.bench.cpp
index 39971c9319e6c5d..ada198892ff968a 100644
--- a/libcxx/test/benchmarks/containers/flat_map.bench.cpp
+++ b/libcxx/test/benchmarks/containers/flat_map.bench.cpp
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
-// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+// REQUIRES: std-at-least-c++26
 
 #include <flat_map>
 #include <string>

>From 00927f02509fcfea763eab4fba69169033c02f48 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 5 Feb 2025 14:35:20 -0500
Subject: [PATCH 04/10] Replace previous set benchmarks

---
 .../containers/ordered_set.bench.cpp          | 232 ------------------
 1 file changed, 232 deletions(-)
 delete mode 100644 libcxx/test/benchmarks/containers/ordered_set.bench.cpp

diff --git a/libcxx/test/benchmarks/containers/ordered_set.bench.cpp b/libcxx/test/benchmarks/containers/ordered_set.bench.cpp
deleted file mode 100644
index cb68902c6dcc88f..000000000000000
--- a/libcxx/test/benchmarks/containers/ordered_set.bench.cpp
+++ /dev/null
@@ -1,232 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-// UNSUPPORTED: c++03, c++11, c++14
-
-#include <algorithm>
-#include <cstdint>
-#include <memory>
-#include <numeric>
-#include <random>
-#include <set>
-#include <string>
-#include <vector>
-
-#include "../CartesianBenchmarks.h"
-#include "benchmark/benchmark.h"
-#include "test_macros.h"
-
-namespace {
-
-enum class HitType { Hit, Miss };
-
-struct AllHitTypes : EnumValuesAsTuple<AllHitTypes, HitType, 2> {
-  static constexpr const char* Names[] = {"Hit", "Miss"};
-};
-
-enum class AccessPattern { Ordered, Random };
-
-struct AllAccessPattern : EnumValuesAsTuple<AllAccessPattern, AccessPattern, 2> {
-  static constexpr const char* Names[] = {"Ordered", "Random"};
-};
-
-void sortKeysBy(std::vector<uint64_t>& Keys, AccessPattern AP) {
-  if (AP == AccessPattern::Random) {
-    std::random_device R;
-    std::mt19937 M(R());
-    std::shuffle(std::begin(Keys), std::end(Keys), M);
-  }
-}
-
-struct TestSets {
-  std::vector<std::set<uint64_t> > Sets;
-  std::vector<uint64_t> Keys;
-};
-
-TestSets makeTestingSets(size_t TableSize, size_t NumTables, HitType Hit, AccessPattern Access) {
-  TestSets R;
-  R.Sets.resize(1);
-
-  for (uint64_t I = 0; I < TableSize; ++I) {
-    R.Sets[0].insert(2 * I);
-    R.Keys.push_back(Hit == HitType::Hit ? 2 * I : 2 * I + 1);
-  }
-  R.Sets.resize(NumTables, R.Sets[0]);
-  sortKeysBy(R.Keys, Access);
-
-  return R;
-}
-
-struct Base {
-  size_t TableSize;
-  size_t NumTables;
-  Base(size_t T, size_t N) : TableSize(T), NumTables(N) {}
-
-  bool skip() const {
-    size_t Total = TableSize * NumTables;
-    return Total < 100 || Total > 1000000;
-  }
-
-  std::string baseName() const {
-    return "_TableSize" + std::to_string(TableSize) + "_NumTables" + std::to_string(NumTables);
-  }
-};
-
-template <class Access>
-struct Create : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    std::vector<uint64_t> Keys(TableSize);
-    std::iota(Keys.begin(), Keys.end(), uint64_t{0});
-    sortKeysBy(Keys, Access());
-
-    while (State.KeepRunningBatch(TableSize * NumTables)) {
-      std::vector<std::set<uint64_t>> Sets(NumTables);
-      for (auto K : Keys) {
-        for (auto& Set : Sets) {
-          benchmark::DoNotOptimize(Set.insert(K));
-        }
-      }
-    }
-  }
-
-  std::string name() const { return "BM_Create" + Access::name() + baseName(); }
-};
-
-template <class Hit, class Access>
-struct Find : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(TableSize, NumTables, Hit(), Access());
-
-    while (State.KeepRunningBatch(TableSize * NumTables)) {
-      for (auto K : Data.Keys) {
-        for (auto& Set : Data.Sets) {
-          benchmark::DoNotOptimize(Set.find(K));
-        }
-      }
-    }
-  }
-
-  std::string name() const { return "BM_Find" + Hit::name() + Access::name() + baseName(); }
-};
-
-template <class Hit, class Access>
-struct FindNeEnd : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(TableSize, NumTables, Hit(), Access());
-
-    while (State.KeepRunningBatch(TableSize * NumTables)) {
-      for (auto K : Data.Keys) {
-        for (auto& Set : Data.Sets) {
-          benchmark::DoNotOptimize(Set.find(K) != Set.end());
-        }
-      }
-    }
-  }
-
-  std::string name() const { return "BM_FindNeEnd" + Hit::name() + Access::name() + baseName(); }
-};
-
-template <class Access>
-struct InsertHit : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(TableSize, NumTables, HitType::Hit, Access());
-
-    while (State.KeepRunningBatch(TableSize * NumTables)) {
-      for (auto K : Data.Keys) {
-        for (auto& Set : Data.Sets) {
-          benchmark::DoNotOptimize(Set.insert(K));
-        }
-      }
-    }
-  }
-
-  std::string name() const { return "BM_InsertHit" + Access::name() + baseName(); }
-};
-
-template <class Access>
-struct InsertMissAndErase : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(TableSize, NumTables, HitType::Miss, Access());
-
-    while (State.KeepRunningBatch(TableSize * NumTables)) {
-      for (auto K : Data.Keys) {
-        for (auto& Set : Data.Sets) {
-          benchmark::DoNotOptimize(Set.erase(Set.insert(K).first));
-        }
-      }
-    }
-  }
-
-  std::string name() const { return "BM_InsertMissAndErase" + Access::name() + baseName(); }
-};
-
-struct IterateRangeFor : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(TableSize, NumTables, HitType::Miss, AccessPattern::Ordered);
-
-    while (State.KeepRunningBatch(TableSize * NumTables)) {
-      for (auto& Set : Data.Sets) {
-        for (auto& V : Set) {
-          benchmark::DoNotOptimize(const_cast<std::set<uint64_t>::reference>(V));
-        }
-      }
-    }
-  }
-
-  std::string name() const { return "BM_IterateRangeFor" + baseName(); }
-};
-
-struct IterateBeginEnd : Base {
-  using Base::Base;
-
-  void run(benchmark::State& State) const {
-    auto Data = makeTestingSets(TableSize, NumTables, HitType::Miss, AccessPattern::Ordered);
-
-    while (State.KeepRunningBatch(TableSize * NumTables)) {
-      for (auto& Set : Data.Sets) {
-        for (auto it = Set.begin(); it != Set.end(); ++it) {
-          benchmark::DoNotOptimize(const_cast<std::set<uint64_t>::reference>(*it));
-        }
-      }
-    }
-  }
-
-  std::string name() const { return "BM_IterateBeginEnd" + baseName(); }
-};
-
-} // namespace
-
-int main(int argc, char** argv) {
-  benchmark::Initialize(&argc, argv);
-  if (benchmark::ReportUnrecognizedArguments(argc, argv))
-    return 1;
-
-  const std::vector<size_t> TableSize{1, 10, 100, 1000, 10000, 100000, 1000000};
-  const std::vector<size_t> NumTables{1, 10, 100, 1000, 10000, 100000, 1000000};
-
-  makeCartesianProductBenchmark<Create, AllAccessPattern>(TableSize, NumTables);
-  makeCartesianProductBenchmark<Find, AllHitTypes, AllAccessPattern>(TableSize, NumTables);
-  makeCartesianProductBenchmark<FindNeEnd, AllHitTypes, AllAccessPattern>(TableSize, NumTables);
-  makeCartesianProductBenchmark<InsertHit, AllAccessPattern>(TableSize, NumTables);
-  makeCartesianProductBenchmark<InsertMissAndErase, AllAccessPattern>(TableSize, NumTables);
-  makeCartesianProductBenchmark<IterateRangeFor>(TableSize, NumTables);
-  makeCartesianProductBenchmark<IterateBeginEnd>(TableSize, NumTables);
-  benchmark::RunSpecifiedBenchmarks();
-}

>From 68fa577087908d2efc67b049102d44d367ff6232 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 5 Feb 2025 14:41:58 -0500
Subject: [PATCH 05/10] Tackle unordered containers too

---
 .../associative_container_benchmarks.h        | 230 ++++++++---------
 .../benchmarks/containers/flat_map.bench.cpp  |   9 +
 .../test/benchmarks/containers/map.bench.cpp  |   9 +
 .../test/benchmarks/containers/set.bench.cpp  |   8 +
 .../containers/unordered_map.bench.cpp        |  34 +++
 .../containers/unordered_set.bench.cpp        | 242 +-----------------
 6 files changed, 180 insertions(+), 352 deletions(-)
 create mode 100644 libcxx/test/benchmarks/containers/unordered_map.bench.cpp

diff --git a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h
index 18dcc3c110770f8..947186dc3674281 100644
--- a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h
@@ -11,48 +11,23 @@
 
 #include <algorithm>
 #include <iterator>
-#include <map>
-#include <flat_map>
 #include <random>
-#include <set>
 #include <string>
 #include <vector>
 
 #include "benchmark/benchmark.h"
 #include "../GenerateInput.h"
-#include "test_macros.h"
 
 namespace support {
 
 template <class Container>
-struct adapt_operations;
-
-template <class K>
-struct adapt_operations<std::set<K>> {
-  using ValueType = typename std::set<K>::value_type;
-  using KeyType   = typename std::set<K>::key_type;
-  static ValueType value_from_key(KeyType const& k) { return k; }
-  static KeyType key_from_value(ValueType const& value) { return value; }
+struct adapt_operations {
+  // using ValueType = ...;
+  // using KeyType   = ...;
+  // static ValueType value_from_key(KeyType const& k);
+  // static KeyType key_from_value(ValueType const& value);
 };
 
-template <class K, class V>
-struct adapt_operations<std::map<K, V>> {
-  using ValueType = typename std::map<K, V>::value_type;
-  using KeyType   = typename std::map<K, V>::key_type;
-  static ValueType value_from_key(KeyType const& k) { return {k, Generate<V>::arbitrary()}; }
-  static KeyType key_from_value(ValueType const& value) { return value.first; }
-};
-
-#if TEST_STD_VER >= 26
-template <class K, class V>
-struct adapt_operations<std::flat_map<K, V>> {
-  using ValueType = typename std::map<K, V>::value_type;
-  using KeyType   = typename std::map<K, V>::key_type;
-  static ValueType value_from_key(KeyType const& k) { return {k, Generate<V>::arbitrary()}; }
-  static KeyType key_from_value(ValueType const& value) { return value.first; }
-};
-#endif
-
 template <class Container>
 void associative_container_benchmarks(std::string container) {
   using Key   = typename Container::key_type;
@@ -67,7 +42,7 @@ void associative_container_benchmarks(std::string container) {
     return std::vector<Key>(keys.begin(), keys.end());
   };
 
-  auto add_dummy_mapped_type = [](std::vector<Key> const& keys) {
+  auto make_value_types = [](std::vector<Key> const& keys) {
     std::vector<Value> kv;
     for (Key const& k : keys)
       kv.push_back(adapt_operations<Container>::value_from_key(k));
@@ -90,7 +65,7 @@ void associative_container_benchmarks(std::string container) {
   /////////////////////////
   benchmark::RegisterBenchmark(container + "::ctor(const&)", [=](auto& st) {
     const std::size_t size = st.range(0);
-    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Container src(in.begin(), in.end());
     ScratchSpace c[BatchSize];
 
@@ -114,7 +89,7 @@ void associative_container_benchmarks(std::string container) {
     std::mt19937 randomness;
     std::vector<Key> keys = generate_unique_keys(size);
     std::shuffle(keys.begin(), keys.end(), randomness);
-    std::vector<Value> in = add_dummy_mapped_type(keys);
+    std::vector<Value> in = make_value_types(keys);
     ScratchSpace c[BatchSize];
 
     while (st.KeepRunningBatch(BatchSize)) {
@@ -136,7 +111,7 @@ void associative_container_benchmarks(std::string container) {
     const std::size_t size = st.range(0);
     std::vector<Key> keys  = generate_unique_keys(size);
     std::sort(keys.begin(), keys.end());
-    std::vector<Value> in = add_dummy_mapped_type(keys);
+    std::vector<Value> in = make_value_types(keys);
     ScratchSpace c[BatchSize];
 
     while (st.KeepRunningBatch(BatchSize)) {
@@ -159,7 +134,7 @@ void associative_container_benchmarks(std::string container) {
   /////////////////////////
   benchmark::RegisterBenchmark(container + "::operator=(const&)", [=](auto& st) {
     const std::size_t size = st.range(0);
-    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Container src(in.begin(), in.end());
     Container c[BatchSize];
 
@@ -183,7 +158,7 @@ void associative_container_benchmarks(std::string container) {
   /////////////////////////
   benchmark::RegisterBenchmark(container + "::insert(value) (already present)", [=](auto& st) {
     const std::size_t size = st.range(0);
-    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    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()));
 
@@ -201,7 +176,7 @@ void associative_container_benchmarks(std::string container) {
 
   benchmark::RegisterBenchmark(container + "::insert(value) (new value)", [=](auto& st) {
     const std::size_t size = st.range(0);
-    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size + 1));
+    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()));
@@ -221,59 +196,63 @@ void associative_container_benchmarks(std::string container) {
     }
   })->Arg(1024);
 
-  benchmark::RegisterBenchmark(container + "::insert(hint, value) (good hint)", [=](auto& st) {
-    const std::size_t size = st.range(0);
-    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size + 1));
-    Value to_insert        = in.back();
-    in.pop_back();
-
-    std::vector<Container> c(BatchSize, Container(in.begin(), in.end()));
-    typename Container::iterator hints[BatchSize];
-    for (std::size_t i = 0; i != BatchSize; ++i) {
-      hints[i] = c[i].lower_bound(get_key(to_insert));
-    }
+  // 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 (requires(Container c, Key k) { c.lower_bound(k); }) {
+    benchmark::RegisterBenchmark(container + "::insert(hint, value) (good hint)", [=](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();
 
-    while (st.KeepRunningBatch(BatchSize)) {
+      std::vector<Container> c(BatchSize, Container(in.begin(), in.end()));
+      typename Container::iterator hints[BatchSize];
       for (std::size_t i = 0; i != BatchSize; ++i) {
-        c[i].insert(hints[i], to_insert);
-        benchmark::DoNotOptimize(c[i]);
-        benchmark::ClobberMemory();
+        hints[i] = c[i].lower_bound(get_key(to_insert));
       }
 
-      st.PauseTiming();
-      for (std::size_t i = 0; i != BatchSize; ++i) {
-        c[i].erase(get_key(to_insert));
-        hints[i] = c[i].lower_bound(get_key(to_insert)); // refresh hints in case of invalidation
+      while (st.KeepRunningBatch(BatchSize)) {
+        for (std::size_t i = 0; i != BatchSize; ++i) {
+          c[i].insert(hints[i], to_insert);
+          benchmark::DoNotOptimize(c[i]);
+          benchmark::ClobberMemory();
+        }
+
+        st.PauseTiming();
+        for (std::size_t i = 0; i != BatchSize; ++i) {
+          c[i].erase(get_key(to_insert));
+          hints[i] = c[i].lower_bound(get_key(to_insert)); // refresh hints in case of invalidation
+        }
+        st.ResumeTiming();
       }
-      st.ResumeTiming();
-    }
-  })->Arg(1024);
+    })->Arg(1024);
 
-  benchmark::RegisterBenchmark(container + "::insert(hint, value) (bad hint)", [=](auto& st) {
-    const std::size_t size = st.range(0);
-    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size + 1));
-    Value to_insert        = in.back();
-    in.pop_back();
-    std::vector<Container> c(BatchSize, Container(in.begin(), in.end()));
+    benchmark::RegisterBenchmark(container + "::insert(hint, value) (bad hint)", [=](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) {
-        c[i].insert(c[i].begin(), to_insert);
-        benchmark::DoNotOptimize(c[i]);
-        benchmark::ClobberMemory();
-      }
+      while (st.KeepRunningBatch(BatchSize)) {
+        for (std::size_t i = 0; i != BatchSize; ++i) {
+          c[i].insert(c[i].begin(), to_insert);
+          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.PauseTiming();
+        for (std::size_t i = 0; i != BatchSize; ++i) {
+          c[i].erase(get_key(to_insert));
+        }
+        st.ResumeTiming();
       }
-      st.ResumeTiming();
-    }
-  })->Arg(1024);
+    })->Arg(1024);
+  }
 
   benchmark::RegisterBenchmark(container + "::insert(iterator, iterator) (all new keys)", [=](auto& st) {
     const std::size_t size = st.range(0);
-    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size + (size / 10)));
+    std::vector<Value> in  = make_value_types(generate_unique_keys(size + (size / 10)));
 
     // Populate a container with a small number of elements, that's what containers will start with.
     std::vector<Value> small;
@@ -296,7 +275,7 @@ void associative_container_benchmarks(std::string container) {
 
   benchmark::RegisterBenchmark(container + "::insert(iterator, iterator) (half new keys)", [=](auto& st) {
     const std::size_t size = st.range(0);
-    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    std::vector<Value> in  = make_value_types(generate_unique_keys(size));
 
     // Populate a container that already contains half the elements we'll try inserting,
     // that's what our container will start with.
@@ -322,7 +301,7 @@ void associative_container_benchmarks(std::string container) {
   /////////////////////////
   benchmark::RegisterBenchmark(container + "::erase(key) (existent)", [=](auto& st) {
     const std::size_t size = st.range(0);
-    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Value element          = in[in.size() / 2]; // pick any element
     std::vector<Container> c(BatchSize, Container(in.begin(), in.end()));
 
@@ -343,7 +322,7 @@ void associative_container_benchmarks(std::string container) {
 
   benchmark::RegisterBenchmark(container + "::erase(key) (non-existent)", [=](auto& st) {
     const std::size_t size = st.range(0);
-    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size + 1));
+    std::vector<Value> in  = make_value_types(generate_unique_keys(size + 1));
     Value element          = in.back();
     in.pop_back();
     Container c(in.begin(), in.end());
@@ -361,7 +340,7 @@ void associative_container_benchmarks(std::string container) {
 
   benchmark::RegisterBenchmark(container + "::erase(iterator)", [=](auto& st) {
     const std::size_t size = st.range(0);
-    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Value element          = in[in.size() / 2]; // pick any element
 
     std::vector<Container> c;
@@ -388,7 +367,7 @@ void associative_container_benchmarks(std::string container) {
 
   benchmark::RegisterBenchmark(container + "::erase(iterator, iterator) (erase half the container)", [=](auto& st) {
     const std::size_t size = st.range(0);
-    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Container c(in.begin(), in.end());
 
     auto first = std::next(c.begin(), c.size() / 4);
@@ -408,7 +387,7 @@ void associative_container_benchmarks(std::string container) {
 
   benchmark::RegisterBenchmark(container + "::clear()", [=](auto& st) {
     const std::size_t size = st.range(0);
-    std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+    std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Container c(in.begin(), in.end());
 
     for (auto _ : st) {
@@ -428,7 +407,7 @@ void associative_container_benchmarks(std::string container) {
   auto bench_with_existent_key = [=](auto func) {
     return [=](auto& st) {
       const std::size_t size = st.range(0);
-      std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size));
+      std::vector<Value> in  = make_value_types(generate_unique_keys(size));
       Value element          = in[in.size() / 2]; // pick any element
       Container c(in.begin(), in.end());
 
@@ -446,7 +425,7 @@ void associative_container_benchmarks(std::string container) {
   auto bench_with_nonexistent_key = [=](auto func) {
     return [=](auto& st) {
       const std::size_t size = st.range(0);
-      std::vector<Value> in  = add_dummy_mapped_type(generate_unique_keys(size + 1));
+      std::vector<Value> in  = make_value_types(generate_unique_keys(size + 1));
       Value element          = in.back();
       in.pop_back();
       Container c(in.begin(), in.end());
@@ -491,44 +470,47 @@ void associative_container_benchmarks(std::string container) {
       }))
       ->Arg(1024);
 
-  benchmark::RegisterBenchmark(
-      container + "::lower_bound(key) (existent)",
-      bench_with_existent_key([=](Container const& c, Value const& element) {
-        return c.lower_bound(get_key(element));
-      }))
-      ->Arg(1024);
-  benchmark::RegisterBenchmark(
-      container + "::lower_bound(key) (non-existent)",
-      bench_with_nonexistent_key([=](Container const& c, Value const& element) {
-        return c.lower_bound(get_key(element));
-      }))
-      ->Arg(1024);
-
-  benchmark::RegisterBenchmark(
-      container + "::upper_bound(key) (existent)",
-      bench_with_existent_key([=](Container const& c, Value const& element) {
-        return c.upper_bound(get_key(element));
-      }))
-      ->Arg(1024);
-  benchmark::RegisterBenchmark(
-      container + "::upper_bound(key) (non-existent)",
-      bench_with_nonexistent_key([=](Container const& c, Value const& element) {
-        return c.upper_bound(get_key(element));
-      }))
-      ->Arg(1024);
-
-  benchmark::RegisterBenchmark(
-      container + "::equal_range(key) (existent)",
-      bench_with_existent_key([=](Container const& c, Value const& element) {
-        return c.equal_range(get_key(element));
-      }))
-      ->Arg(1024);
-  benchmark::RegisterBenchmark(
-      container + "::equal_range(key) (non-existent)",
-      bench_with_nonexistent_key([=](Container const& c, Value const& element) {
-        return c.equal_range(get_key(element));
-      }))
-      ->Arg(1024);
+  // Only for ordered containers
+  if constexpr (requires(Container c, Key key) { c.lower_bound(key); }) {
+    benchmark::RegisterBenchmark(
+        container + "::lower_bound(key) (existent)",
+        bench_with_existent_key([=](Container const& c, Value const& element) {
+          return c.lower_bound(get_key(element));
+        }))
+        ->Arg(1024);
+    benchmark::RegisterBenchmark(
+        container + "::lower_bound(key) (non-existent)",
+        bench_with_nonexistent_key([=](Container const& c, Value const& element) {
+          return c.lower_bound(get_key(element));
+        }))
+        ->Arg(1024);
+
+    benchmark::RegisterBenchmark(
+        container + "::upper_bound(key) (existent)",
+        bench_with_existent_key([=](Container const& c, Value const& element) {
+          return c.upper_bound(get_key(element));
+        }))
+        ->Arg(1024);
+    benchmark::RegisterBenchmark(
+        container + "::upper_bound(key) (non-existent)",
+        bench_with_nonexistent_key([=](Container const& c, Value const& element) {
+          return c.upper_bound(get_key(element));
+        }))
+        ->Arg(1024);
+
+    benchmark::RegisterBenchmark(
+        container + "::equal_range(key) (existent)",
+        bench_with_existent_key([=](Container const& c, Value const& element) {
+          return c.equal_range(get_key(element));
+        }))
+        ->Arg(1024);
+    benchmark::RegisterBenchmark(
+        container + "::equal_range(key) (non-existent)",
+        bench_with_nonexistent_key([=](Container const& c, Value const& element) {
+          return c.equal_range(get_key(element));
+        }))
+        ->Arg(1024);
+  }
 }
 
 } // namespace support
diff --git a/libcxx/test/benchmarks/containers/flat_map.bench.cpp b/libcxx/test/benchmarks/containers/flat_map.bench.cpp
index ada198892ff968a..1c50e52cf80438f 100644
--- a/libcxx/test/benchmarks/containers/flat_map.bench.cpp
+++ b/libcxx/test/benchmarks/containers/flat_map.bench.cpp
@@ -12,8 +12,17 @@
 #include <string>
 
 #include "associative_container_benchmarks.h"
+#include "../GenerateInput.h"
 #include "benchmark/benchmark.h"
 
+template <class K, class V>
+struct support::adapt_operations<std::flat_map<K, V>> {
+  using ValueType = typename std::map<K, V>::value_type;
+  using KeyType   = typename std::map<K, V>::key_type;
+  static ValueType value_from_key(KeyType const& k) { return {k, Generate<V>::arbitrary()}; }
+  static KeyType key_from_value(ValueType const& value) { return value.first; }
+};
+
 int main(int argc, char** argv) {
   support::associative_container_benchmarks<std::flat_map<int, int>>("std::flat_map<int, int>");
   support::associative_container_benchmarks<std::flat_map<std::string, int>>("std::flat_map<std::string, int>");
diff --git a/libcxx/test/benchmarks/containers/map.bench.cpp b/libcxx/test/benchmarks/containers/map.bench.cpp
index 5c5ba7cc9d3db01..73c9e806ce1f0d8 100644
--- a/libcxx/test/benchmarks/containers/map.bench.cpp
+++ b/libcxx/test/benchmarks/containers/map.bench.cpp
@@ -12,8 +12,17 @@
 #include <string>
 
 #include "associative_container_benchmarks.h"
+#include "../GenerateInput.h"
 #include "benchmark/benchmark.h"
 
+template <class K, class V>
+struct support::adapt_operations<std::map<K, V>> {
+  using ValueType = typename std::map<K, V>::value_type;
+  using KeyType   = typename std::map<K, V>::key_type;
+  static ValueType value_from_key(KeyType const& k) { return {k, Generate<V>::arbitrary()}; }
+  static KeyType key_from_value(ValueType const& value) { return value.first; }
+};
+
 int main(int argc, char** argv) {
   support::associative_container_benchmarks<std::map<int, int>>("std::map<int, int>");
   support::associative_container_benchmarks<std::map<std::string, int>>("std::map<std::string, int>");
diff --git a/libcxx/test/benchmarks/containers/set.bench.cpp b/libcxx/test/benchmarks/containers/set.bench.cpp
index 6a8de0862f2ae3c..42f0777f967b2e9 100644
--- a/libcxx/test/benchmarks/containers/set.bench.cpp
+++ b/libcxx/test/benchmarks/containers/set.bench.cpp
@@ -13,6 +13,14 @@
 #include "associative_container_benchmarks.h"
 #include "benchmark/benchmark.h"
 
+template <class K>
+struct support::adapt_operations<std::set<K>> {
+  using ValueType = typename std::set<K>::value_type;
+  using KeyType   = typename std::set<K>::key_type;
+  static ValueType value_from_key(KeyType const& k) { return k; }
+  static KeyType key_from_value(ValueType const& value) { return value; }
+};
+
 int main(int argc, char** argv) {
   support::associative_container_benchmarks<std::set<int>>("std::set<int>");
 
diff --git a/libcxx/test/benchmarks/containers/unordered_map.bench.cpp b/libcxx/test/benchmarks/containers/unordered_map.bench.cpp
new file mode 100644
index 000000000000000..51c6cf1e3cf11fe
--- /dev/null
+++ b/libcxx/test/benchmarks/containers/unordered_map.bench.cpp
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+#include <string>
+#include <unordered_map>
+
+#include "associative_container_benchmarks.h"
+#include "../GenerateInput.h"
+#include "benchmark/benchmark.h"
+
+template <class K, class V>
+struct support::adapt_operations<std::unordered_map<K, V>> {
+  using ValueType = typename std::unordered_map<K, V>::value_type;
+  using KeyType   = typename std::unordered_map<K, V>::key_type;
+  static ValueType value_from_key(KeyType const& k) { return {k, Generate<V>::arbitrary()}; }
+  static KeyType key_from_value(ValueType const& value) { return value.first; }
+};
+
+int main(int argc, char** argv) {
+  support::associative_container_benchmarks<std::unordered_map<int, int>>("std::unordered_map<int, int>");
+  support::associative_container_benchmarks<std::unordered_map<std::string, int>>("std::unordered_map<std::string, int>");
+
+  benchmark::Initialize(&argc, argv);
+  benchmark::RunSpecifiedBenchmarks();
+  benchmark::Shutdown();
+  return 0;
+}
diff --git a/libcxx/test/benchmarks/containers/unordered_set.bench.cpp b/libcxx/test/benchmarks/containers/unordered_set.bench.cpp
index ad8d0feaa043659..f1661aaeb39483b 100644
--- a/libcxx/test/benchmarks/containers/unordered_set.bench.cpp
+++ b/libcxx/test/benchmarks/containers/unordered_set.bench.cpp
@@ -8,238 +8,24 @@
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
 
-#include <cstdint>
-#include <cstdlib>
-#include <cstring>
-#include <functional>
 #include <unordered_set>
-#include <vector>
 
+#include "associative_container_benchmarks.h"
 #include "benchmark/benchmark.h"
 
-#include "container_benchmarks.h"
-#include "../GenerateInput.h"
-#include "test_macros.h"
-
-using namespace ContainerBenchmarks;
-
-constexpr std::size_t TestNumInputs = 1024;
-
-// The purpose of this hash function is to NOT be implemented as the identity function,
-// which is how std::hash is implemented for smaller integral types.
-struct NonIdentityScalarHash : std::hash<unsigned long long> {};
-
-// The sole purpose of this comparator is to be used in BM_Rehash, where
-// we need something slow enough to be easily noticable in benchmark results.
-// The default implementation of operator== for strings seems to be a little
-// too fast for that specific benchmark to reliably show a noticeable
-// improvement, but unoptimized bytewise comparison fits just right.
-// Early return is there just for convenience, since we only compare strings
-// of equal length in BM_Rehash.
-struct SlowStringEq {
-  SlowStringEq() = default;
-  inline TEST_ALWAYS_INLINE bool operator()(const std::string& lhs, const std::string& rhs) const {
-    if (lhs.size() != rhs.size())
-      return false;
-
-    bool eq = true;
-    for (size_t i = 0; i < lhs.size(); ++i) {
-      eq &= lhs[i] == rhs[i];
-    }
-    return eq;
-  }
+template <class K>
+struct support::adapt_operations<std::unordered_set<K>> {
+  using ValueType = typename std::unordered_set<K>::value_type;
+  using KeyType   = typename std::unordered_set<K>::key_type;
+  static ValueType value_from_key(KeyType const& k) { return k; }
+  static KeyType key_from_value(ValueType const& value) { return value; }
 };
 
-//----------------------------------------------------------------------------//
-//                       BM_InsertValue
-// ---------------------------------------------------------------------------//
-
-// Sorted Ascending //
-BENCHMARK_CAPTURE(
-    BM_InsertValue, unordered_set_uint32, std::unordered_set<uint32_t>{}, getRandomIntegerInputs<uint32_t>)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(
-    BM_InsertValue, unordered_set_uint32_sorted, std::unordered_set<uint32_t>{}, getSortedIntegerInputs<uint32_t>)
-    ->Arg(TestNumInputs);
-
-// Top Bytes //
-BENCHMARK_CAPTURE(BM_InsertValue,
-                  unordered_set_top_bits_uint32,
-                  std::unordered_set<uint32_t>{},
-                  getSortedTopBitsIntegerInputs<uint32_t>)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_InsertValueRehash,
-                  unordered_set_top_bits_uint32,
-                  std::unordered_set<uint32_t, NonIdentityScalarHash>{},
-                  getSortedTopBitsIntegerInputs<uint32_t>)
-    ->Arg(TestNumInputs);
-
-// String //
-BENCHMARK_CAPTURE(BM_InsertValue, unordered_set_string, std::unordered_set<std::string>{}, getRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_InsertValueRehash, unordered_set_string, std::unordered_set<std::string>{}, getRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-// Prefixed String //
-BENCHMARK_CAPTURE(
-    BM_InsertValue, unordered_set_prefixed_string, std::unordered_set<std::string>{}, getPrefixedRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_InsertValueRehash,
-                  unordered_set_prefixed_string,
-                  std::unordered_set<std::string>{},
-                  getPrefixedRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-//----------------------------------------------------------------------------//
-//                         BM_Find
-// ---------------------------------------------------------------------------//
-
-// Random //
-BENCHMARK_CAPTURE(
-    BM_Find, unordered_set_random_uint64, std::unordered_set<uint64_t>{}, getRandomIntegerInputs<uint64_t>)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_FindRehash,
-                  unordered_set_random_uint64,
-                  std::unordered_set<uint64_t, NonIdentityScalarHash>{},
-                  getRandomIntegerInputs<uint64_t>)
-    ->Arg(TestNumInputs);
-
-// Sorted //
-BENCHMARK_CAPTURE(
-    BM_Find, unordered_set_sorted_uint64, std::unordered_set<uint64_t>{}, getSortedIntegerInputs<uint64_t>)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_FindRehash,
-                  unordered_set_sorted_uint64,
-                  std::unordered_set<uint64_t, NonIdentityScalarHash>{},
-                  getSortedIntegerInputs<uint64_t>)
-    ->Arg(TestNumInputs);
-
-// Sorted //
-#ifndef TEST_HAS_NO_INT128
-BENCHMARK_CAPTURE(BM_Find,
-                  unordered_set_sorted_uint128,
-                  std::unordered_set<__uint128_t>{},
-                  getSortedTopBitsIntegerInputs<__uint128_t>)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_FindRehash,
-                  unordered_set_sorted_uint128,
-                  std::unordered_set<__uint128_t>{},
-                  getSortedTopBitsIntegerInputs<__uint128_t>)
-    ->Arg(TestNumInputs);
-#endif
-
-// Sorted //
-BENCHMARK_CAPTURE(
-    BM_Find, unordered_set_sorted_uint32, std::unordered_set<uint32_t>{}, getSortedIntegerInputs<uint32_t>)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_FindRehash,
-                  unordered_set_sorted_uint32,
-                  std::unordered_set<uint32_t, NonIdentityScalarHash>{},
-                  getSortedIntegerInputs<uint32_t>)
-    ->Arg(TestNumInputs);
-
-// Sorted Ascending //
-BENCHMARK_CAPTURE(
-    BM_Find, unordered_set_sorted_large_uint64, std::unordered_set<uint64_t>{}, getSortedLargeIntegerInputs<uint64_t>)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_FindRehash,
-                  unordered_set_sorted_large_uint64,
-                  std::unordered_set<uint64_t, NonIdentityScalarHash>{},
-                  getSortedLargeIntegerInputs<uint64_t>)
-    ->Arg(TestNumInputs);
-
-// Top Bits //
-BENCHMARK_CAPTURE(
-    BM_Find, unordered_set_top_bits_uint64, std::unordered_set<uint64_t>{}, getSortedTopBitsIntegerInputs<uint64_t>)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_FindRehash,
-                  unordered_set_top_bits_uint64,
-                  std::unordered_set<uint64_t, NonIdentityScalarHash>{},
-                  getSortedTopBitsIntegerInputs<uint64_t>)
-    ->Arg(TestNumInputs);
-
-// String //
-BENCHMARK_CAPTURE(BM_Find, unordered_set_string, std::unordered_set<std::string>{}, getRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_FindRehash, unordered_set_string, std::unordered_set<std::string>{}, getRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-// Prefixed String //
-BENCHMARK_CAPTURE(
-    BM_Find, unordered_set_prefixed_string, std::unordered_set<std::string>{}, getPrefixedRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(
-    BM_FindRehash, unordered_set_prefixed_string, std::unordered_set<std::string>{}, getPrefixedRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-//----------------------------------------------------------------------------//
-//                         BM_Rehash
-// ---------------------------------------------------------------------------//
-
-BENCHMARK_CAPTURE(BM_Rehash,
-                  unordered_set_string_arg,
-                  std::unordered_set<std::string, std::hash<std::string>, SlowStringEq>{},
-                  getRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_Rehash, unordered_set_int_arg, std::unordered_set<int>{}, getRandomIntegerInputs<int>)
-    ->Arg(TestNumInputs);
-
-//----------------------------------------------------------------------------//
-//                         BM_Compare
-// ---------------------------------------------------------------------------//
-
-BENCHMARK_CAPTURE(
-    BM_Compare_same_container, unordered_set_string, std::unordered_set<std::string>{}, getRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_Compare_same_container, unordered_set_int, std::unordered_set<int>{}, getRandomIntegerInputs<int>)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(
-    BM_Compare_different_containers, unordered_set_string, std::unordered_set<std::string>{}, getRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(
-    BM_Compare_different_containers, unordered_set_int, std::unordered_set<int>{}, getRandomIntegerInputs<int>)
-    ->Arg(TestNumInputs);
-
-///////////////////////////////////////////////////////////////////////////////
-BENCHMARK_CAPTURE(BM_InsertDuplicate, unordered_set_int, std::unordered_set<int>{}, getRandomIntegerInputs<int>)
-    ->Arg(TestNumInputs);
-BENCHMARK_CAPTURE(BM_InsertDuplicate, unordered_set_string, std::unordered_set<std::string>{}, getRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_EmplaceDuplicate, unordered_set_int, std::unordered_set<int>{}, getRandomIntegerInputs<int>)
-    ->Arg(TestNumInputs);
-BENCHMARK_CAPTURE(BM_EmplaceDuplicate, unordered_set_string, std::unordered_set<std::string>{}, getRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(
-    BM_InsertDuplicate, unordered_set_int_insert_arg, std::unordered_set<int>{}, getRandomIntegerInputs<int>)
-    ->Arg(TestNumInputs);
-BENCHMARK_CAPTURE(
-    BM_InsertDuplicate, unordered_set_string_insert_arg, std::unordered_set<std::string>{}, getRandomStringInputs)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(
-    BM_EmplaceDuplicate, unordered_set_int_insert_arg, std::unordered_set<int>{}, getRandomIntegerInputs<unsigned>)
-    ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(
-    BM_EmplaceDuplicate, unordered_set_string_arg, std::unordered_set<std::string>{}, getRandomCStringInputs)
-    ->Arg(TestNumInputs);
+int main(int argc, char** argv) {
+  support::associative_container_benchmarks<std::unordered_set<int>>("std::unordered_set<int>");
 
-BENCHMARK_MAIN();
+  benchmark::Initialize(&argc, argv);
+  benchmark::RunSpecifiedBenchmarks();
+  benchmark::Shutdown();
+  return 0;
+}

>From c7788163548d42bbb92e6625776622a77c2df9dd Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 5 Feb 2025 15:15:48 -0500
Subject: [PATCH 06/10] Handle multi-key containers too

---
 .../associative_container_benchmarks.h        | 34 +++++++++++++++--
 .../benchmarks/containers/flat_map.bench.cpp  |  4 ++
 .../test/benchmarks/containers/map.bench.cpp  |  4 ++
 .../benchmarks/containers/multimap.bench.cpp  | 37 ++++++++++++++++++
 .../benchmarks/containers/multiset.bench.cpp  | 34 +++++++++++++++++
 .../test/benchmarks/containers/set.bench.cpp  |  4 ++
 .../containers/unordered_map.bench.cpp        |  7 +++-
 .../containers/unordered_multimap.bench.cpp   | 38 +++++++++++++++++++
 .../containers/unordered_multiset.bench.cpp   | 34 +++++++++++++++++
 .../containers/unordered_set.bench.cpp        |  4 ++
 10 files changed, 196 insertions(+), 4 deletions(-)
 create mode 100644 libcxx/test/benchmarks/containers/multimap.bench.cpp
 create mode 100644 libcxx/test/benchmarks/containers/multiset.bench.cpp
 create mode 100644 libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp
 create mode 100644 libcxx/test/benchmarks/containers/unordered_multiset.bench.cpp

diff --git a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h
index 947186dc3674281..252b211e0456b29 100644
--- a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h
@@ -26,6 +26,9 @@ struct adapt_operations {
   // using KeyType   = ...;
   // static ValueType value_from_key(KeyType const& k);
   // static KeyType key_from_value(ValueType const& value);
+
+  // using InsertionResult = ...;
+  // static Container::iterator get_iterator(InsertionResult const&);
 };
 
 template <class Container>
@@ -169,8 +172,17 @@ void associative_container_benchmarks(std::string container) {
         benchmark::ClobberMemory();
       }
 
-      // There is no cleanup to do, since associative containers don't insert
-      // if the key is already present.
+      st.PauseTiming();
+      // Unique-key containers will not insert above, but multi-key containers will insert
+      // the key a second time. Restore the invariant that there's a single copy of each
+      // key in the container.
+      for (std::size_t i = 0; i != BatchSize; ++i) {
+        auto const& key = get_key(to_insert);
+        if (c[i].count(key) > 1) {
+          c[i].erase(key);
+        }
+      }
+      st.ResumeTiming();
     }
   })->Arg(1024);
 
@@ -359,7 +371,7 @@ void associative_container_benchmarks(std::string container) {
 
       st.PauseTiming();
       for (std::size_t i = 0; i != BatchSize; ++i) {
-        iterators[i] = c[i].insert(element).first;
+        iterators[i] = adapt_operations<Container>::get_iterator(c[i].insert(element));
       }
       st.ResumeTiming();
     }
@@ -401,6 +413,22 @@ void associative_container_benchmarks(std::string container) {
     }
   })->Arg(1024);
 
+  /////////////////////////
+  // Capacity
+  /////////////////////////
+  benchmark::RegisterBenchmark(container + "::size()", [=](auto& st) {
+    const std::size_t size = st.range(0);
+    std::vector<Value> in  = make_value_types(generate_unique_keys(size));
+    Container c(in.begin(), in.end());
+
+    for (auto _ : st) {
+      auto res = c.size();
+      benchmark::DoNotOptimize(c);
+      benchmark::DoNotOptimize(res);
+      benchmark::ClobberMemory();
+    }
+  })->Arg(1024);
+
   /////////////////////////
   // Query
   /////////////////////////
diff --git a/libcxx/test/benchmarks/containers/flat_map.bench.cpp b/libcxx/test/benchmarks/containers/flat_map.bench.cpp
index 1c50e52cf80438f..013d1c4dcc464b1 100644
--- a/libcxx/test/benchmarks/containers/flat_map.bench.cpp
+++ b/libcxx/test/benchmarks/containers/flat_map.bench.cpp
@@ -10,6 +10,7 @@
 
 #include <flat_map>
 #include <string>
+#include <utility>
 
 #include "associative_container_benchmarks.h"
 #include "../GenerateInput.h"
@@ -21,6 +22,9 @@ struct support::adapt_operations<std::flat_map<K, V>> {
   using KeyType   = typename std::map<K, V>::key_type;
   static ValueType value_from_key(KeyType const& k) { return {k, Generate<V>::arbitrary()}; }
   static KeyType key_from_value(ValueType const& value) { return value.first; }
+
+  using InsertionResult = std::pair<typename std::flat_map<K, V>::iterator, bool>;
+  static auto get_iterator(InsertionResult const& result) { return result.first; }
 };
 
 int main(int argc, char** argv) {
diff --git a/libcxx/test/benchmarks/containers/map.bench.cpp b/libcxx/test/benchmarks/containers/map.bench.cpp
index 73c9e806ce1f0d8..21d6af6af40c585 100644
--- a/libcxx/test/benchmarks/containers/map.bench.cpp
+++ b/libcxx/test/benchmarks/containers/map.bench.cpp
@@ -10,6 +10,7 @@
 
 #include <map>
 #include <string>
+#include <utility>
 
 #include "associative_container_benchmarks.h"
 #include "../GenerateInput.h"
@@ -21,6 +22,9 @@ struct support::adapt_operations<std::map<K, V>> {
   using KeyType   = typename std::map<K, V>::key_type;
   static ValueType value_from_key(KeyType const& k) { return {k, Generate<V>::arbitrary()}; }
   static KeyType key_from_value(ValueType const& value) { return value.first; }
+
+  using InsertionResult = std::pair<typename std::map<K, V>::iterator, bool>;
+  static auto get_iterator(InsertionResult const& result) { return result.first; }
 };
 
 int main(int argc, char** argv) {
diff --git a/libcxx/test/benchmarks/containers/multimap.bench.cpp b/libcxx/test/benchmarks/containers/multimap.bench.cpp
new file mode 100644
index 000000000000000..1a983090d3826b3
--- /dev/null
+++ b/libcxx/test/benchmarks/containers/multimap.bench.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+#include <map>
+#include <string>
+
+#include "associative_container_benchmarks.h"
+#include "../GenerateInput.h"
+#include "benchmark/benchmark.h"
+
+template <class K, class V>
+struct support::adapt_operations<std::multimap<K, V>> {
+  using ValueType = typename std::multimap<K, V>::value_type;
+  using KeyType   = typename std::multimap<K, V>::key_type;
+  static ValueType value_from_key(KeyType const& k) { return {k, Generate<V>::arbitrary()}; }
+  static KeyType key_from_value(ValueType const& value) { return value.first; }
+
+  using InsertionResult = typename std::multimap<K, V>::iterator;
+  static auto get_iterator(InsertionResult const& result) { return result; }
+};
+
+int main(int argc, char** argv) {
+  support::associative_container_benchmarks<std::multimap<int, int>>("std::multimap<int, int>");
+  support::associative_container_benchmarks<std::multimap<std::string, int>>("std::multimap<std::string, int>");
+
+  benchmark::Initialize(&argc, argv);
+  benchmark::RunSpecifiedBenchmarks();
+  benchmark::Shutdown();
+  return 0;
+}
diff --git a/libcxx/test/benchmarks/containers/multiset.bench.cpp b/libcxx/test/benchmarks/containers/multiset.bench.cpp
new file mode 100644
index 000000000000000..894f159a52e4adb
--- /dev/null
+++ b/libcxx/test/benchmarks/containers/multiset.bench.cpp
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+#include <set>
+
+#include "associative_container_benchmarks.h"
+#include "benchmark/benchmark.h"
+
+template <class K>
+struct support::adapt_operations<std::multiset<K>> {
+  using ValueType = typename std::multiset<K>::value_type;
+  using KeyType   = typename std::multiset<K>::key_type;
+  static ValueType value_from_key(KeyType const& k) { return k; }
+  static KeyType key_from_value(ValueType const& value) { return value; }
+
+  using InsertionResult = typename std::multiset<K>::iterator;
+  static auto get_iterator(InsertionResult const& result) { return result; }
+};
+
+int main(int argc, char** argv) {
+  support::associative_container_benchmarks<std::multiset<int>>("std::multiset<int>");
+
+  benchmark::Initialize(&argc, argv);
+  benchmark::RunSpecifiedBenchmarks();
+  benchmark::Shutdown();
+  return 0;
+}
diff --git a/libcxx/test/benchmarks/containers/set.bench.cpp b/libcxx/test/benchmarks/containers/set.bench.cpp
index 42f0777f967b2e9..6b7b142c792ba0f 100644
--- a/libcxx/test/benchmarks/containers/set.bench.cpp
+++ b/libcxx/test/benchmarks/containers/set.bench.cpp
@@ -9,6 +9,7 @@
 // UNSUPPORTED: c++03, c++11, c++14, c++17
 
 #include <set>
+#include <utility>
 
 #include "associative_container_benchmarks.h"
 #include "benchmark/benchmark.h"
@@ -19,6 +20,9 @@ struct support::adapt_operations<std::set<K>> {
   using KeyType   = typename std::set<K>::key_type;
   static ValueType value_from_key(KeyType const& k) { return k; }
   static KeyType key_from_value(ValueType const& value) { return value; }
+
+  using InsertionResult = std::pair<typename std::set<K>::iterator, bool>;
+  static auto get_iterator(InsertionResult const& result) { return result.first; }
 };
 
 int main(int argc, char** argv) {
diff --git a/libcxx/test/benchmarks/containers/unordered_map.bench.cpp b/libcxx/test/benchmarks/containers/unordered_map.bench.cpp
index 51c6cf1e3cf11fe..707cc3e584462b3 100644
--- a/libcxx/test/benchmarks/containers/unordered_map.bench.cpp
+++ b/libcxx/test/benchmarks/containers/unordered_map.bench.cpp
@@ -10,6 +10,7 @@
 
 #include <string>
 #include <unordered_map>
+#include <utility>
 
 #include "associative_container_benchmarks.h"
 #include "../GenerateInput.h"
@@ -21,11 +22,15 @@ struct support::adapt_operations<std::unordered_map<K, V>> {
   using KeyType   = typename std::unordered_map<K, V>::key_type;
   static ValueType value_from_key(KeyType const& k) { return {k, Generate<V>::arbitrary()}; }
   static KeyType key_from_value(ValueType const& value) { return value.first; }
+
+  using InsertionResult = std::pair<typename std::unordered_map<K, V>::iterator, bool>;
+  static auto get_iterator(InsertionResult const& result) { return result.first; }
 };
 
 int main(int argc, char** argv) {
   support::associative_container_benchmarks<std::unordered_map<int, int>>("std::unordered_map<int, int>");
-  support::associative_container_benchmarks<std::unordered_map<std::string, int>>("std::unordered_map<std::string, int>");
+  support::associative_container_benchmarks<std::unordered_map<std::string, int>>(
+      "std::unordered_map<std::string, int>");
 
   benchmark::Initialize(&argc, argv);
   benchmark::RunSpecifiedBenchmarks();
diff --git a/libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp b/libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp
new file mode 100644
index 000000000000000..ae7caf0f453c795
--- /dev/null
+++ b/libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+#include <string>
+#include <unordered_map>
+
+#include "associative_container_benchmarks.h"
+#include "../GenerateInput.h"
+#include "benchmark/benchmark.h"
+
+template <class K, class V>
+struct support::adapt_operations<std::unordered_multimap<K, V>> {
+  using ValueType = typename std::unordered_multimap<K, V>::value_type;
+  using KeyType   = typename std::unordered_multimap<K, V>::key_type;
+  static ValueType value_from_key(KeyType const& k) { return {k, Generate<V>::arbitrary()}; }
+  static KeyType key_from_value(ValueType const& value) { return value.first; }
+
+  using InsertionResult = typename std::unordered_multimap<K, V>::iterator;
+  static auto get_iterator(InsertionResult const& result) { return result; }
+};
+
+int main(int argc, char** argv) {
+  support::associative_container_benchmarks<std::unordered_multimap<int, int>>("std::unordered_multimap<int, int>");
+  support::associative_container_benchmarks<std::unordered_multimap<std::string, int>>(
+      "std::unordered_multimap<std::string, int>");
+
+  benchmark::Initialize(&argc, argv);
+  benchmark::RunSpecifiedBenchmarks();
+  benchmark::Shutdown();
+  return 0;
+}
diff --git a/libcxx/test/benchmarks/containers/unordered_multiset.bench.cpp b/libcxx/test/benchmarks/containers/unordered_multiset.bench.cpp
new file mode 100644
index 000000000000000..4888b01bfeba07e
--- /dev/null
+++ b/libcxx/test/benchmarks/containers/unordered_multiset.bench.cpp
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+#include <unordered_set>
+
+#include "associative_container_benchmarks.h"
+#include "benchmark/benchmark.h"
+
+template <class K>
+struct support::adapt_operations<std::unordered_multiset<K>> {
+  using ValueType = typename std::unordered_multiset<K>::value_type;
+  using KeyType   = typename std::unordered_multiset<K>::key_type;
+  static ValueType value_from_key(KeyType const& k) { return k; }
+  static KeyType key_from_value(ValueType const& value) { return value; }
+
+  using InsertionResult = typename std::unordered_multiset<K>::iterator;
+  static auto get_iterator(InsertionResult const& result) { return result; }
+};
+
+int main(int argc, char** argv) {
+  support::associative_container_benchmarks<std::unordered_multiset<int>>("std::unordered_multiset<int>");
+
+  benchmark::Initialize(&argc, argv);
+  benchmark::RunSpecifiedBenchmarks();
+  benchmark::Shutdown();
+  return 0;
+}
diff --git a/libcxx/test/benchmarks/containers/unordered_set.bench.cpp b/libcxx/test/benchmarks/containers/unordered_set.bench.cpp
index f1661aaeb39483b..56420bdaadfbf94 100644
--- a/libcxx/test/benchmarks/containers/unordered_set.bench.cpp
+++ b/libcxx/test/benchmarks/containers/unordered_set.bench.cpp
@@ -9,6 +9,7 @@
 // UNSUPPORTED: c++03, c++11, c++14, c++17
 
 #include <unordered_set>
+#include <utility>
 
 #include "associative_container_benchmarks.h"
 #include "benchmark/benchmark.h"
@@ -19,6 +20,9 @@ struct support::adapt_operations<std::unordered_set<K>> {
   using KeyType   = typename std::unordered_set<K>::key_type;
   static ValueType value_from_key(KeyType const& k) { return k; }
   static KeyType key_from_value(ValueType const& value) { return value; }
+
+  using InsertionResult = std::pair<typename std::unordered_set<K>::iterator, bool>;
+  static auto get_iterator(InsertionResult const& result) { return result.first; }
 };
 
 int main(int argc, char** argv) {

>From d732fe382cc17747137a1d2e7b9a75b8458ad36f Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 5 Feb 2025 15:18:46 -0500
Subject: [PATCH 07/10] Move associative containers to subdirectory

---
 .../{ => associative}/associative_container_benchmarks.h        | 2 +-
 .../benchmarks/containers/{ => associative}/flat_map.bench.cpp  | 2 +-
 .../test/benchmarks/containers/{ => associative}/map.bench.cpp  | 2 +-
 .../benchmarks/containers/{ => associative}/multimap.bench.cpp  | 2 +-
 .../benchmarks/containers/{ => associative}/multiset.bench.cpp  | 0
 .../test/benchmarks/containers/{ => associative}/set.bench.cpp  | 0
 .../containers/{ => associative}/unordered_map.bench.cpp        | 2 +-
 .../containers/{ => associative}/unordered_multimap.bench.cpp   | 2 +-
 .../containers/{ => associative}/unordered_multiset.bench.cpp   | 0
 .../containers/{ => associative}/unordered_set.bench.cpp        | 0
 10 files changed, 6 insertions(+), 6 deletions(-)
 rename libcxx/test/benchmarks/containers/{ => associative}/associative_container_benchmarks.h (99%)
 rename libcxx/test/benchmarks/containers/{ => associative}/flat_map.bench.cpp (97%)
 rename libcxx/test/benchmarks/containers/{ => associative}/map.bench.cpp (97%)
 rename libcxx/test/benchmarks/containers/{ => associative}/multimap.bench.cpp (97%)
 rename libcxx/test/benchmarks/containers/{ => associative}/multiset.bench.cpp (100%)
 rename libcxx/test/benchmarks/containers/{ => associative}/set.bench.cpp (100%)
 rename libcxx/test/benchmarks/containers/{ => associative}/unordered_map.bench.cpp (97%)
 rename libcxx/test/benchmarks/containers/{ => associative}/unordered_multimap.bench.cpp (97%)
 rename libcxx/test/benchmarks/containers/{ => associative}/unordered_multiset.bench.cpp (100%)
 rename libcxx/test/benchmarks/containers/{ => associative}/unordered_set.bench.cpp (100%)

diff --git a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
similarity index 99%
rename from libcxx/test/benchmarks/containers/associative_container_benchmarks.h
rename to libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
index 252b211e0456b29..ddf4a5000324ff1 100644
--- a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
@@ -16,7 +16,7 @@
 #include <vector>
 
 #include "benchmark/benchmark.h"
-#include "../GenerateInput.h"
+#include "../../GenerateInput.h"
 
 namespace support {
 
diff --git a/libcxx/test/benchmarks/containers/flat_map.bench.cpp b/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp
similarity index 97%
rename from libcxx/test/benchmarks/containers/flat_map.bench.cpp
rename to libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp
index 013d1c4dcc464b1..d166eb2089e6787 100644
--- a/libcxx/test/benchmarks/containers/flat_map.bench.cpp
+++ b/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp
@@ -13,7 +13,7 @@
 #include <utility>
 
 #include "associative_container_benchmarks.h"
-#include "../GenerateInput.h"
+#include "../../GenerateInput.h"
 #include "benchmark/benchmark.h"
 
 template <class K, class V>
diff --git a/libcxx/test/benchmarks/containers/map.bench.cpp b/libcxx/test/benchmarks/containers/associative/map.bench.cpp
similarity index 97%
rename from libcxx/test/benchmarks/containers/map.bench.cpp
rename to libcxx/test/benchmarks/containers/associative/map.bench.cpp
index 21d6af6af40c585..bd664dbb56ee769 100644
--- a/libcxx/test/benchmarks/containers/map.bench.cpp
+++ b/libcxx/test/benchmarks/containers/associative/map.bench.cpp
@@ -13,7 +13,7 @@
 #include <utility>
 
 #include "associative_container_benchmarks.h"
-#include "../GenerateInput.h"
+#include "../../GenerateInput.h"
 #include "benchmark/benchmark.h"
 
 template <class K, class V>
diff --git a/libcxx/test/benchmarks/containers/multimap.bench.cpp b/libcxx/test/benchmarks/containers/associative/multimap.bench.cpp
similarity index 97%
rename from libcxx/test/benchmarks/containers/multimap.bench.cpp
rename to libcxx/test/benchmarks/containers/associative/multimap.bench.cpp
index 1a983090d3826b3..15a0b573081bbd1 100644
--- a/libcxx/test/benchmarks/containers/multimap.bench.cpp
+++ b/libcxx/test/benchmarks/containers/associative/multimap.bench.cpp
@@ -12,7 +12,7 @@
 #include <string>
 
 #include "associative_container_benchmarks.h"
-#include "../GenerateInput.h"
+#include "../../GenerateInput.h"
 #include "benchmark/benchmark.h"
 
 template <class K, class V>
diff --git a/libcxx/test/benchmarks/containers/multiset.bench.cpp b/libcxx/test/benchmarks/containers/associative/multiset.bench.cpp
similarity index 100%
rename from libcxx/test/benchmarks/containers/multiset.bench.cpp
rename to libcxx/test/benchmarks/containers/associative/multiset.bench.cpp
diff --git a/libcxx/test/benchmarks/containers/set.bench.cpp b/libcxx/test/benchmarks/containers/associative/set.bench.cpp
similarity index 100%
rename from libcxx/test/benchmarks/containers/set.bench.cpp
rename to libcxx/test/benchmarks/containers/associative/set.bench.cpp
diff --git a/libcxx/test/benchmarks/containers/unordered_map.bench.cpp b/libcxx/test/benchmarks/containers/associative/unordered_map.bench.cpp
similarity index 97%
rename from libcxx/test/benchmarks/containers/unordered_map.bench.cpp
rename to libcxx/test/benchmarks/containers/associative/unordered_map.bench.cpp
index 707cc3e584462b3..66844a9ecdf79c8 100644
--- a/libcxx/test/benchmarks/containers/unordered_map.bench.cpp
+++ b/libcxx/test/benchmarks/containers/associative/unordered_map.bench.cpp
@@ -13,7 +13,7 @@
 #include <utility>
 
 #include "associative_container_benchmarks.h"
-#include "../GenerateInput.h"
+#include "../../GenerateInput.h"
 #include "benchmark/benchmark.h"
 
 template <class K, class V>
diff --git a/libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp b/libcxx/test/benchmarks/containers/associative/unordered_multimap.bench.cpp
similarity index 97%
rename from libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp
rename to libcxx/test/benchmarks/containers/associative/unordered_multimap.bench.cpp
index ae7caf0f453c795..64ac8d4ee862f6a 100644
--- a/libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp
+++ b/libcxx/test/benchmarks/containers/associative/unordered_multimap.bench.cpp
@@ -12,7 +12,7 @@
 #include <unordered_map>
 
 #include "associative_container_benchmarks.h"
-#include "../GenerateInput.h"
+#include "../../GenerateInput.h"
 #include "benchmark/benchmark.h"
 
 template <class K, class V>
diff --git a/libcxx/test/benchmarks/containers/unordered_multiset.bench.cpp b/libcxx/test/benchmarks/containers/associative/unordered_multiset.bench.cpp
similarity index 100%
rename from libcxx/test/benchmarks/containers/unordered_multiset.bench.cpp
rename to libcxx/test/benchmarks/containers/associative/unordered_multiset.bench.cpp
diff --git a/libcxx/test/benchmarks/containers/unordered_set.bench.cpp b/libcxx/test/benchmarks/containers/associative/unordered_set.bench.cpp
similarity index 100%
rename from libcxx/test/benchmarks/containers/unordered_set.bench.cpp
rename to libcxx/test/benchmarks/containers/associative/unordered_set.bench.cpp

>From 90b4ecd97ec965fd9bb48f9b6f1bd7d450ca2ede Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 5 Feb 2025 15:27:03 -0500
Subject: [PATCH 08/10] Provide centralized control over the size of containers
 used

---
 .../associative_container_benchmarks.h        | 173 ++++++++----------
 1 file changed, 74 insertions(+), 99 deletions(-)

diff --git a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
index ddf4a5000324ff1..72aad6a35da7582 100644
--- a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
@@ -54,6 +54,10 @@ void associative_container_benchmarks(std::string container) {
 
   auto get_key = [](Value const& v) { return adapt_operations<Container>::key_from_value(v); };
 
+  auto bench = [&](std::string operation, auto f) {
+    benchmark::RegisterBenchmark(container + "::" + operation, f)->Arg(1024);
+  };
+
   // These benchmarks are structured to perform the operation being benchmarked
   // a small number of times at each iteration, in order to offset the cost of
   // PauseTiming() and ResumeTiming().
@@ -66,7 +70,7 @@ void associative_container_benchmarks(std::string container) {
   /////////////////////////
   // Constructors
   /////////////////////////
-  benchmark::RegisterBenchmark(container + "::ctor(const&)", [=](auto& st) {
+  bench("ctor(const&)", [=](auto& st) {
     const std::size_t size = st.range(0);
     std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Container src(in.begin(), in.end());
@@ -85,9 +89,9 @@ void associative_container_benchmarks(std::string container) {
       }
       st.ResumeTiming();
     }
-  })->Arg(1024);
+  });
 
-  benchmark::RegisterBenchmark(container + "::ctor(iterator, iterator) (unsorted sequence)", [=](auto& st) {
+  bench("ctor(iterator, iterator) (unsorted sequence)", [=](auto& st) {
     const std::size_t size = st.range(0);
     std::mt19937 randomness;
     std::vector<Key> keys = generate_unique_keys(size);
@@ -108,9 +112,9 @@ void associative_container_benchmarks(std::string container) {
       }
       st.ResumeTiming();
     }
-  })->Arg(1024);
+  });
 
-  benchmark::RegisterBenchmark(container + "::ctor(iterator, iterator) (sorted sequence)", [=](auto& st) {
+  bench("ctor(iterator, iterator) (sorted sequence)", [=](auto& st) {
     const std::size_t size = st.range(0);
     std::vector<Key> keys  = generate_unique_keys(size);
     std::sort(keys.begin(), keys.end());
@@ -130,12 +134,12 @@ void associative_container_benchmarks(std::string container) {
       }
       st.ResumeTiming();
     }
-  })->Arg(1024);
+  });
 
   /////////////////////////
   // Assignment
   /////////////////////////
-  benchmark::RegisterBenchmark(container + "::operator=(const&)", [=](auto& st) {
+  bench("operator=(const&)", [=](auto& st) {
     const std::size_t size = st.range(0);
     std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Container src(in.begin(), in.end());
@@ -154,12 +158,12 @@ void associative_container_benchmarks(std::string container) {
       }
       st.ResumeTiming();
     }
-  })->Arg(1024);
+  });
 
   /////////////////////////
   // Insertion
   /////////////////////////
-  benchmark::RegisterBenchmark(container + "::insert(value) (already present)", [=](auto& st) {
+  bench("insert(value) (already present)", [=](auto& st) {
     const std::size_t size = st.range(0);
     std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Value to_insert        = in[in.size() / 2]; // pick any existing value
@@ -184,9 +188,9 @@ void associative_container_benchmarks(std::string container) {
       }
       st.ResumeTiming();
     }
-  })->Arg(1024);
+  });
 
-  benchmark::RegisterBenchmark(container + "::insert(value) (new value)", [=](auto& st) {
+  bench("insert(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();
@@ -206,12 +210,12 @@ void associative_container_benchmarks(std::string container) {
       }
       st.ResumeTiming();
     }
-  })->Arg(1024);
+  });
 
   // 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 (requires(Container c, Key k) { c.lower_bound(k); }) {
-    benchmark::RegisterBenchmark(container + "::insert(hint, value) (good hint)", [=](auto& st) {
+    bench("insert(hint, value) (good hint)", [=](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();
@@ -237,9 +241,9 @@ void associative_container_benchmarks(std::string container) {
         }
         st.ResumeTiming();
       }
-    })->Arg(1024);
+    });
 
-    benchmark::RegisterBenchmark(container + "::insert(hint, value) (bad hint)", [=](auto& st) {
+    bench("insert(hint, value) (bad hint)", [=](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();
@@ -259,10 +263,10 @@ void associative_container_benchmarks(std::string container) {
         }
         st.ResumeTiming();
       }
-    })->Arg(1024);
+    });
   }
 
-  benchmark::RegisterBenchmark(container + "::insert(iterator, iterator) (all new keys)", [=](auto& st) {
+  bench("insert(iterator, iterator) (all new keys)", [=](auto& st) {
     const std::size_t size = st.range(0);
     std::vector<Value> in  = make_value_types(generate_unique_keys(size + (size / 10)));
 
@@ -283,9 +287,9 @@ void associative_container_benchmarks(std::string container) {
       c = Container(small.begin(), small.end());
       st.ResumeTiming();
     }
-  })->Arg(1024);
+  });
 
-  benchmark::RegisterBenchmark(container + "::insert(iterator, iterator) (half new keys)", [=](auto& st) {
+  bench("insert(iterator, iterator) (half new keys)", [=](auto& st) {
     const std::size_t size = st.range(0);
     std::vector<Value> in  = make_value_types(generate_unique_keys(size));
 
@@ -306,12 +310,12 @@ void associative_container_benchmarks(std::string container) {
       c = Container(small.begin(), small.end());
       st.ResumeTiming();
     }
-  })->Arg(1024);
+  });
 
   /////////////////////////
   // Erasure
   /////////////////////////
-  benchmark::RegisterBenchmark(container + "::erase(key) (existent)", [=](auto& st) {
+  bench("erase(key) (existent)", [=](auto& st) {
     const std::size_t size = st.range(0);
     std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Value element          = in[in.size() / 2]; // pick any element
@@ -330,9 +334,9 @@ void associative_container_benchmarks(std::string container) {
       }
       st.ResumeTiming();
     }
-  })->Arg(1024);
+  });
 
-  benchmark::RegisterBenchmark(container + "::erase(key) (non-existent)", [=](auto& st) {
+  bench("erase(key) (non-existent)", [=](auto& st) {
     const std::size_t size = st.range(0);
     std::vector<Value> in  = make_value_types(generate_unique_keys(size + 1));
     Value element          = in.back();
@@ -348,9 +352,9 @@ void associative_container_benchmarks(std::string container) {
 
       // no cleanup required because we erased a non-existent element
     }
-  })->Arg(1024);
+  });
 
-  benchmark::RegisterBenchmark(container + "::erase(iterator)", [=](auto& st) {
+  bench("erase(iterator)", [=](auto& st) {
     const std::size_t size = st.range(0);
     std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Value element          = in[in.size() / 2]; // pick any element
@@ -375,9 +379,9 @@ void associative_container_benchmarks(std::string container) {
       }
       st.ResumeTiming();
     }
-  })->Arg(1024);
+  });
 
-  benchmark::RegisterBenchmark(container + "::erase(iterator, iterator) (erase half the container)", [=](auto& st) {
+  bench("erase(iterator, iterator) (erase half the container)", [=](auto& st) {
     const std::size_t size = st.range(0);
     std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Container c(in.begin(), in.end());
@@ -395,9 +399,9 @@ void associative_container_benchmarks(std::string container) {
       last  = std::next(c.begin(), 3 * (c.size() / 4));
       st.ResumeTiming();
     }
-  })->Arg(1024);
+  });
 
-  benchmark::RegisterBenchmark(container + "::clear()", [=](auto& st) {
+  bench("clear()", [=](auto& st) {
     const std::size_t size = st.range(0);
     std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Container c(in.begin(), in.end());
@@ -411,12 +415,12 @@ void associative_container_benchmarks(std::string container) {
       c = Container(in.begin(), in.end());
       st.ResumeTiming();
     }
-  })->Arg(1024);
+  });
 
   /////////////////////////
   // Capacity
   /////////////////////////
-  benchmark::RegisterBenchmark(container + "::size()", [=](auto& st) {
+  bench("size()", [=](auto& st) {
     const std::size_t size = st.range(0);
     std::vector<Value> in  = make_value_types(generate_unique_keys(size));
     Container c(in.begin(), in.end());
@@ -427,7 +431,7 @@ void associative_container_benchmarks(std::string container) {
       benchmark::DoNotOptimize(res);
       benchmark::ClobberMemory();
     }
-  })->Arg(1024);
+  });
 
   /////////////////////////
   // Query
@@ -469,75 +473,46 @@ void associative_container_benchmarks(std::string container) {
     };
   };
 
-  benchmark::RegisterBenchmark(
-      container + "::find(key) (existent)",
-      bench_with_existent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); }))
-      ->Arg(1024);
-  benchmark::RegisterBenchmark(
-      container + "::find(key) (non-existent)",
-      bench_with_nonexistent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); }))
-      ->Arg(1024);
-
-  benchmark::RegisterBenchmark(
-      container + "::count(key) (existent)",
-      bench_with_existent_key([=](Container const& c, Value const& element) { return c.count(get_key(element)); }))
-      ->Arg(1024);
-  benchmark::RegisterBenchmark(
-      container + "::count(key) (non-existent)",
-      bench_with_nonexistent_key([=](Container const& c, Value const& element) { return c.count(get_key(element)); }))
-      ->Arg(1024);
-
-  benchmark::RegisterBenchmark(
-      container + "::contains(key) (existent)",
-      bench_with_existent_key([=](Container const& c, Value const& element) { return c.contains(get_key(element)); }))
-      ->Arg(1024);
-  benchmark::RegisterBenchmark(
-      container + "::contains(key) (non-existent)",
-      bench_with_nonexistent_key([=](Container const& c, Value const& element) {
-        return c.contains(get_key(element));
-      }))
-      ->Arg(1024);
+  bench("find(key) (existent)",
+        bench_with_existent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); }));
+  bench("find(key) (non-existent)",
+        bench_with_nonexistent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); }));
+
+  bench("count(key) (existent)",
+        bench_with_existent_key([=](Container const& c, Value const& element) { return c.count(get_key(element)); }));
+  bench("count(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) {
+          return c.count(get_key(element));
+        }));
+
+  bench("contains(key) (existent)", bench_with_existent_key([=](Container const& c, Value const& element) {
+          return c.contains(get_key(element));
+        }));
+  bench("contains(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) {
+          return c.contains(get_key(element));
+        }));
 
   // Only for ordered containers
-  if constexpr (requires(Container c, Key key) { c.lower_bound(key); }) {
-    benchmark::RegisterBenchmark(
-        container + "::lower_bound(key) (existent)",
-        bench_with_existent_key([=](Container const& c, Value const& element) {
-          return c.lower_bound(get_key(element));
-        }))
-        ->Arg(1024);
-    benchmark::RegisterBenchmark(
-        container + "::lower_bound(key) (non-existent)",
-        bench_with_nonexistent_key([=](Container const& c, Value const& element) {
-          return c.lower_bound(get_key(element));
-        }))
-        ->Arg(1024);
-
-    benchmark::RegisterBenchmark(
-        container + "::upper_bound(key) (existent)",
-        bench_with_existent_key([=](Container const& c, Value const& element) {
-          return c.upper_bound(get_key(element));
-        }))
-        ->Arg(1024);
-    benchmark::RegisterBenchmark(
-        container + "::upper_bound(key) (non-existent)",
-        bench_with_nonexistent_key([=](Container const& c, Value const& element) {
-          return c.upper_bound(get_key(element));
-        }))
-        ->Arg(1024);
-
-    benchmark::RegisterBenchmark(
-        container + "::equal_range(key) (existent)",
-        bench_with_existent_key([=](Container const& c, Value const& element) {
-          return c.equal_range(get_key(element));
-        }))
-        ->Arg(1024);
-    benchmark::RegisterBenchmark(
-        container + "::equal_range(key) (non-existent)",
-        bench_with_nonexistent_key([=](Container const& c, Value const& element) {
-          return c.equal_range(get_key(element));
-        }))
-        ->Arg(1024);
+  if constexpr (requires(Container c, Key k) { c.lower_bound(k); }) {
+    bench("lower_bound(key) (existent)", bench_with_existent_key([=](Container const& c, Value const& element) {
+            return c.lower_bound(get_key(element));
+          }));
+    bench("lower_bound(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) {
+            return c.lower_bound(get_key(element));
+          }));
+
+    bench("upper_bound(key) (existent)", bench_with_existent_key([=](Container const& c, Value const& element) {
+            return c.upper_bound(get_key(element));
+          }));
+    bench("upper_bound(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) {
+            return c.upper_bound(get_key(element));
+          }));
+
+    bench("equal_range(key) (existent)", bench_with_existent_key([=](Container const& c, Value const& element) {
+            return c.equal_range(get_key(element));
+          }));
+    bench("equal_range(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) {
+            return c.equal_range(get_key(element));
+          }));
   }
 }
 

>From 658cd30c7e28808e69fef9232105ca71b7cdc3e4 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 5 Feb 2025 15:53:12 -0500
Subject: [PATCH 09/10] Call DoNotOptimize on the result of operations

---
 .../associative_container_benchmarks.h        | 28 ++++++++++++-------
 1 file changed, 18 insertions(+), 10 deletions(-)

diff --git a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
index 72aad6a35da7582..3c29c751dc1cc4e 100644
--- a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
@@ -171,7 +171,8 @@ void associative_container_benchmarks(std::string container) {
 
     while (st.KeepRunningBatch(BatchSize)) {
       for (std::size_t i = 0; i != BatchSize; ++i) {
-        c[i].insert(to_insert);
+        auto result = c[i].insert(to_insert);
+        benchmark::DoNotOptimize(result);
         benchmark::DoNotOptimize(c[i]);
         benchmark::ClobberMemory();
       }
@@ -199,7 +200,8 @@ void associative_container_benchmarks(std::string container) {
 
     while (st.KeepRunningBatch(BatchSize)) {
       for (std::size_t i = 0; i != BatchSize; ++i) {
-        c[i].insert(to_insert);
+        auto result = c[i].insert(to_insert);
+        benchmark::DoNotOptimize(result);
         benchmark::DoNotOptimize(c[i]);
         benchmark::ClobberMemory();
       }
@@ -229,7 +231,8 @@ void associative_container_benchmarks(std::string container) {
 
       while (st.KeepRunningBatch(BatchSize)) {
         for (std::size_t i = 0; i != BatchSize; ++i) {
-          c[i].insert(hints[i], to_insert);
+          auto result = c[i].insert(hints[i], to_insert);
+          benchmark::DoNotOptimize(result);
           benchmark::DoNotOptimize(c[i]);
           benchmark::ClobberMemory();
         }
@@ -252,7 +255,8 @@ void associative_container_benchmarks(std::string container) {
 
       while (st.KeepRunningBatch(BatchSize)) {
         for (std::size_t i = 0; i != BatchSize; ++i) {
-          c[i].insert(c[i].begin(), to_insert);
+          auto result = c[i].insert(c[i].begin(), to_insert);
+          benchmark::DoNotOptimize(result);
           benchmark::DoNotOptimize(c[i]);
           benchmark::ClobberMemory();
         }
@@ -323,7 +327,8 @@ void associative_container_benchmarks(std::string container) {
 
     while (st.KeepRunningBatch(BatchSize)) {
       for (std::size_t i = 0; i != BatchSize; ++i) {
-        c[i].erase(get_key(element));
+        auto result = c[i].erase(get_key(element));
+        benchmark::DoNotOptimize(result);
         benchmark::DoNotOptimize(c[i]);
         benchmark::ClobberMemory();
       }
@@ -345,7 +350,8 @@ void associative_container_benchmarks(std::string container) {
 
     while (st.KeepRunningBatch(BatchSize)) {
       for (std::size_t i = 0; i != BatchSize; ++i) {
-        c.erase(get_key(element));
+        auto result = c.erase(get_key(element));
+        benchmark::DoNotOptimize(result);
         benchmark::DoNotOptimize(c);
         benchmark::ClobberMemory();
       }
@@ -368,7 +374,8 @@ void associative_container_benchmarks(std::string container) {
 
     while (st.KeepRunningBatch(BatchSize)) {
       for (std::size_t i = 0; i != BatchSize; ++i) {
-        c[i].erase(iterators[i]);
+        auto result = c[i].erase(iterators[i]);
+        benchmark::DoNotOptimize(result);
         benchmark::DoNotOptimize(c[i]);
         benchmark::ClobberMemory();
       }
@@ -389,7 +396,8 @@ void associative_container_benchmarks(std::string container) {
     auto first = std::next(c.begin(), c.size() / 4);
     auto last  = std::next(c.begin(), 3 * (c.size() / 4));
     for (auto _ : st) {
-      c.erase(first, last);
+      auto result = c.erase(first, last);
+      benchmark::DoNotOptimize(result);
       benchmark::DoNotOptimize(c);
       benchmark::ClobberMemory();
 
@@ -426,9 +434,9 @@ void associative_container_benchmarks(std::string container) {
     Container c(in.begin(), in.end());
 
     for (auto _ : st) {
-      auto res = c.size();
+      auto result = c.size();
+      benchmark::DoNotOptimize(result);
       benchmark::DoNotOptimize(c);
-      benchmark::DoNotOptimize(res);
       benchmark::ClobberMemory();
     }
   });

>From a4546d85d513641f2d19078176d0c962b1db053e Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 5 Feb 2025 16:26:46 -0500
Subject: [PATCH 10/10] Adjust batch size and run for a few container sizes

---
 .../containers/associative/associative_container_benchmarks.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
index 3c29c751dc1cc4e..10e21eb2f9370ae 100644
--- a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h
@@ -55,13 +55,13 @@ void associative_container_benchmarks(std::string container) {
   auto get_key = [](Value const& v) { return adapt_operations<Container>::key_from_value(v); };
 
   auto bench = [&](std::string operation, auto f) {
-    benchmark::RegisterBenchmark(container + "::" + operation, f)->Arg(1024);
+    benchmark::RegisterBenchmark(container + "::" + operation, f)->Arg(32)->Arg(1024)->Arg(8192);
   };
 
   // These benchmarks are structured to perform the operation being benchmarked
   // a small number of times at each iteration, in order to offset the cost of
   // PauseTiming() and ResumeTiming().
-  static constexpr std::size_t BatchSize = 10;
+  static constexpr std::size_t BatchSize = 32;
 
   struct alignas(Container) ScratchSpace {
     char storage[sizeof(Container)];



More information about the libcxx-commits mailing list