Fixes #108624 This allows `flat_map::insert(Iter, Iter)` to directly forward to underlying containers' `insert(Iter, Iter)`, instead of inserting one element at a time, when input models "product iterator". atm, `flat_map::iterator` and `zip_view::iterator` are "product iterator"s. This gives about almost 10x speed up in my benchmark with -03 (for both before and after) ```cpp Benchmark Time CPU Time Old Time New CPU Old CPU New ----------------------------------------------------------------------------------------------------------------------------------------------- flat_map::insert_product_iterator_flat_map/32 -0.5028 -0.5320 149 74 149 70 flat_map::insert_product_iterator_flat_map/1024 -0.8617 -0.8618 3113 430 3112 430 flat_map::insert_product_iterator_flat_map/8192 -0.8877 -0.8877 26682 2995 26679 2995 flat_map::insert_product_iterator_flat_map/65536 -0.8769 -0.8769 226235 27844 226221 27841 flat_map::insert_product_iterator_zip/32 -0.5844 -0.5844 162 67 162 67 flat_map::insert_product_iterator_zip/1024 -0.8754 -0.8754 3427 427 3427 427 flat_map::insert_product_iterator_zip/8192 -0.8934 -0.8934 28134 3000 28132 3000 flat_map::insert_product_iterator_zip/65536 -0.8783 -0.8783 229783 27960 229767 27958 OVERALL_GEOMEAN -0.8319 -0.8332 0 0 0 0 ``` --------- Co-authored-by: Louis Dionne <ldionne.2@gmail.com>
559 lines
18 KiB
C++
559 lines
18 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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 <random>
|
|
#include <string>
|
|
#include <ranges>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "benchmark/benchmark.h"
|
|
#include "../../GenerateInput.h"
|
|
#include "test_macros.h"
|
|
|
|
namespace support {
|
|
|
|
template <class Container>
|
|
struct adapt_operations {
|
|
// using ValueType = ...;
|
|
// 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>
|
|
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 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));
|
|
return kv;
|
|
};
|
|
|
|
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(32)->Arg(1024)->Arg(8192);
|
|
};
|
|
|
|
static constexpr bool is_multi_key_container =
|
|
!std::is_same_v<typename adapt_operations<Container>::InsertionResult,
|
|
std::pair<typename Container::iterator, bool>>;
|
|
|
|
static constexpr bool is_ordered_container = requires(Container c, Key k) { c.lower_bound(k); };
|
|
|
|
static constexpr bool is_map_like = requires { typename Container::mapped_type; };
|
|
|
|
// 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 = 32;
|
|
|
|
struct alignas(Container) ScratchSpace {
|
|
char storage[sizeof(Container)];
|
|
};
|
|
|
|
/////////////////////////
|
|
// Constructors
|
|
/////////////////////////
|
|
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());
|
|
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();
|
|
}
|
|
});
|
|
|
|
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);
|
|
std::shuffle(keys.begin(), keys.end(), randomness);
|
|
std::vector<Value> in = make_value_types(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();
|
|
}
|
|
});
|
|
|
|
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());
|
|
std::vector<Value> in = make_value_types(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();
|
|
}
|
|
});
|
|
|
|
/////////////////////////
|
|
// Assignment
|
|
/////////////////////////
|
|
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());
|
|
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();
|
|
}
|
|
});
|
|
|
|
/////////////////////////
|
|
// Insertion
|
|
/////////////////////////
|
|
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
|
|
std::vector<Container> c(BatchSize, Container(in.begin(), in.end()));
|
|
typename Container::iterator inserted[BatchSize];
|
|
|
|
while (st.KeepRunningBatch(BatchSize)) {
|
|
for (std::size_t i = 0; i != BatchSize; ++i) {
|
|
inserted[i] = adapt_operations<Container>::get_iterator(c[i].insert(to_insert));
|
|
benchmark::DoNotOptimize(inserted[i]);
|
|
benchmark::DoNotOptimize(c[i]);
|
|
benchmark::ClobberMemory();
|
|
}
|
|
|
|
if constexpr (is_multi_key_container) {
|
|
st.PauseTiming();
|
|
for (std::size_t i = 0; i != BatchSize; ++i) {
|
|
c[i].erase(inserted[i]);
|
|
}
|
|
st.ResumeTiming();
|
|
}
|
|
}
|
|
});
|
|
|
|
bench("insert(value) (new value)", [=](auto& st) {
|
|
const std::size_t size = st.range(0);
|
|
std::vector<Value> in = make_value_types(generate_unique_keys(size + 1));
|
|
Value to_insert = in.back();
|
|
in.pop_back();
|
|
std::vector<Container> c(BatchSize, Container(in.begin(), in.end()));
|
|
|
|
while (st.KeepRunningBatch(BatchSize)) {
|
|
for (std::size_t i = 0; i != BatchSize; ++i) {
|
|
auto result = c[i].insert(to_insert);
|
|
benchmark::DoNotOptimize(result);
|
|
benchmark::DoNotOptimize(c[i]);
|
|
benchmark::ClobberMemory();
|
|
}
|
|
|
|
st.PauseTiming();
|
|
for (std::size_t i = 0; i != BatchSize; ++i) {
|
|
c[i].erase(get_key(to_insert));
|
|
}
|
|
st.ResumeTiming();
|
|
}
|
|
});
|
|
|
|
// The insert(hint, ...) methods are only relevant for ordered containers, and we lack
|
|
// a good way to compute a hint for unordered ones.
|
|
if constexpr (is_ordered_container) {
|
|
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();
|
|
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) {
|
|
auto result = c[i].insert(hints[i], to_insert);
|
|
benchmark::DoNotOptimize(result);
|
|
benchmark::DoNotOptimize(c[i]);
|
|
benchmark::ClobberMemory();
|
|
}
|
|
|
|
st.PauseTiming();
|
|
for (std::size_t i = 0; i != BatchSize; ++i) {
|
|
c[i].erase(get_key(to_insert));
|
|
hints[i] = c[i].lower_bound(get_key(to_insert)); // refresh hints in case of invalidation
|
|
}
|
|
st.ResumeTiming();
|
|
}
|
|
});
|
|
|
|
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();
|
|
in.pop_back();
|
|
std::vector<Container> c(BatchSize, Container(in.begin(), in.end()));
|
|
|
|
while (st.KeepRunningBatch(BatchSize)) {
|
|
for (std::size_t i = 0; i != BatchSize; ++i) {
|
|
auto result = c[i].insert(c[i].begin(), to_insert);
|
|
benchmark::DoNotOptimize(result);
|
|
benchmark::DoNotOptimize(c[i]);
|
|
benchmark::ClobberMemory();
|
|
}
|
|
|
|
st.PauseTiming();
|
|
for (std::size_t i = 0; i != BatchSize; ++i) {
|
|
c[i].erase(get_key(to_insert));
|
|
}
|
|
st.ResumeTiming();
|
|
}
|
|
});
|
|
}
|
|
|
|
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)));
|
|
|
|
// 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 ([[maybe_unused]] auto _ : st) {
|
|
c.insert(in.begin(), in.end());
|
|
benchmark::DoNotOptimize(c);
|
|
benchmark::ClobberMemory();
|
|
|
|
st.PauseTiming();
|
|
c = Container(small.begin(), small.end());
|
|
st.ResumeTiming();
|
|
}
|
|
});
|
|
|
|
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));
|
|
|
|
// 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 ([[maybe_unused]] auto _ : st) {
|
|
c.insert(in.begin(), in.end());
|
|
benchmark::DoNotOptimize(c);
|
|
benchmark::ClobberMemory();
|
|
|
|
st.PauseTiming();
|
|
c = Container(small.begin(), small.end());
|
|
st.ResumeTiming();
|
|
}
|
|
});
|
|
|
|
if constexpr (is_map_like) {
|
|
bench("insert(iterator, iterator) (product_iterator from same type)", [=](auto& st) {
|
|
const std::size_t size = st.range(0);
|
|
std::vector<Value> in = make_value_types(generate_unique_keys(size + (size / 10)));
|
|
Container source(in.begin(), in.end());
|
|
|
|
Container c;
|
|
|
|
for ([[maybe_unused]] auto _ : st) {
|
|
c.insert(source.begin(), source.end());
|
|
benchmark::DoNotOptimize(c);
|
|
benchmark::ClobberMemory();
|
|
|
|
st.PauseTiming();
|
|
c = Container();
|
|
st.ResumeTiming();
|
|
}
|
|
});
|
|
|
|
#if TEST_STD_VER >= 23
|
|
bench("insert(iterator, iterator) (product_iterator from zip_view)", [=](auto& st) {
|
|
const std::size_t size = st.range(0);
|
|
std::vector<Key> keys = generate_unique_keys(size + (size / 10));
|
|
std::sort(keys.begin(), keys.end());
|
|
std::vector<typename Container::mapped_type> mapped(keys.size());
|
|
|
|
auto source = std::views::zip(keys, mapped);
|
|
|
|
Container c;
|
|
|
|
for ([[maybe_unused]] auto _ : st) {
|
|
c.insert(source.begin(), source.end());
|
|
benchmark::DoNotOptimize(c);
|
|
benchmark::ClobberMemory();
|
|
|
|
st.PauseTiming();
|
|
c = Container();
|
|
st.ResumeTiming();
|
|
}
|
|
});
|
|
#endif
|
|
}
|
|
/////////////////////////
|
|
// Erasure
|
|
/////////////////////////
|
|
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
|
|
std::vector<Container> c(BatchSize, Container(in.begin(), in.end()));
|
|
|
|
while (st.KeepRunningBatch(BatchSize)) {
|
|
for (std::size_t i = 0; i != BatchSize; ++i) {
|
|
auto result = c[i].erase(get_key(element));
|
|
benchmark::DoNotOptimize(result);
|
|
benchmark::DoNotOptimize(c[i]);
|
|
benchmark::ClobberMemory();
|
|
}
|
|
|
|
st.PauseTiming();
|
|
for (std::size_t i = 0; i != BatchSize; ++i) {
|
|
c[i].insert(element);
|
|
}
|
|
st.ResumeTiming();
|
|
}
|
|
});
|
|
|
|
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 + BatchSize));
|
|
std::vector<Key> keys;
|
|
for (std::size_t i = 0; i != BatchSize; ++i) {
|
|
keys.push_back(get_key(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 = c.erase(keys[i]);
|
|
benchmark::DoNotOptimize(result);
|
|
benchmark::DoNotOptimize(c);
|
|
benchmark::ClobberMemory();
|
|
}
|
|
|
|
// no cleanup required because we erased a non-existent element
|
|
}
|
|
});
|
|
|
|
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
|
|
|
|
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) {
|
|
auto result = c[i].erase(iterators[i]);
|
|
benchmark::DoNotOptimize(result);
|
|
benchmark::DoNotOptimize(c[i]);
|
|
benchmark::ClobberMemory();
|
|
}
|
|
|
|
st.PauseTiming();
|
|
for (std::size_t i = 0; i != BatchSize; ++i) {
|
|
iterators[i] = adapt_operations<Container>::get_iterator(c[i].insert(element));
|
|
}
|
|
st.ResumeTiming();
|
|
}
|
|
});
|
|
|
|
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());
|
|
|
|
auto first = std::next(c.begin(), c.size() / 4);
|
|
auto last = std::next(c.begin(), 3 * (c.size() / 4));
|
|
for ([[maybe_unused]] auto _ : st) {
|
|
auto result = c.erase(first, last);
|
|
benchmark::DoNotOptimize(result);
|
|
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();
|
|
}
|
|
});
|
|
|
|
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());
|
|
|
|
for ([[maybe_unused]] auto _ : st) {
|
|
c.clear();
|
|
benchmark::DoNotOptimize(c);
|
|
benchmark::ClobberMemory();
|
|
|
|
st.PauseTiming();
|
|
c = Container(in.begin(), in.end());
|
|
st.ResumeTiming();
|
|
}
|
|
});
|
|
|
|
/////////////////////////
|
|
// Query
|
|
/////////////////////////
|
|
auto with_existent_key = [=](auto func) {
|
|
return [=](auto& st) {
|
|
const std::size_t size = st.range(0);
|
|
std::vector<Value> in = make_value_types(generate_unique_keys(size));
|
|
// Pick any `BatchSize` number of elements
|
|
std::vector<Key> keys;
|
|
for (std::size_t i = 0; i < in.size(); i += (in.size() / BatchSize)) {
|
|
keys.push_back(get_key(in.at(i)));
|
|
}
|
|
Container c(in.begin(), in.end());
|
|
|
|
while (st.KeepRunningBatch(BatchSize)) {
|
|
for (std::size_t i = 0; i != BatchSize; ++i) {
|
|
auto result = func(c, keys[i]);
|
|
benchmark::DoNotOptimize(c);
|
|
benchmark::DoNotOptimize(result);
|
|
benchmark::ClobberMemory();
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
auto with_nonexistent_key = [=](auto func) {
|
|
return [=](auto& st) {
|
|
const std::size_t size = st.range(0);
|
|
std::vector<Value> in = make_value_types(generate_unique_keys(size + BatchSize));
|
|
std::vector<Key> keys;
|
|
for (std::size_t i = 0; i != BatchSize; ++i) {
|
|
keys.push_back(get_key(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, keys[i]);
|
|
benchmark::DoNotOptimize(c);
|
|
benchmark::DoNotOptimize(result);
|
|
benchmark::ClobberMemory();
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
auto find = [](Container const& c, Key const& key) { return c.find(key); };
|
|
bench("find(key) (existent)", with_existent_key(find));
|
|
bench("find(key) (non-existent)", with_nonexistent_key(find));
|
|
|
|
auto count = [](Container const& c, Key const& key) { return c.count(key); };
|
|
bench("count(key) (existent)", with_existent_key(count));
|
|
bench("count(key) (non-existent)", with_nonexistent_key(count));
|
|
|
|
auto contains = [](Container const& c, Key const& key) { return c.contains(key); };
|
|
bench("contains(key) (existent)", with_existent_key(contains));
|
|
bench("contains(key) (non-existent)", with_nonexistent_key(contains));
|
|
|
|
if constexpr (is_ordered_container) {
|
|
auto lower_bound = [](Container const& c, Key const& key) { return c.lower_bound(key); };
|
|
bench("lower_bound(key) (existent)", with_existent_key(lower_bound));
|
|
bench("lower_bound(key) (non-existent)", with_nonexistent_key(lower_bound));
|
|
|
|
auto upper_bound = [](Container const& c, Key const& key) { return c.upper_bound(key); };
|
|
bench("upper_bound(key) (existent)", with_existent_key(upper_bound));
|
|
bench("upper_bound(key) (non-existent)", with_nonexistent_key(upper_bound));
|
|
|
|
auto equal_range = [](Container const& c, Key const& key) { return c.equal_range(key); };
|
|
bench("equal_range(key) (existent)", with_existent_key(equal_range));
|
|
bench("equal_range(key) (non-existent)", with_nonexistent_key(equal_range));
|
|
}
|
|
}
|
|
|
|
} // namespace support
|
|
|
|
#endif // TEST_BENCHMARKS_CONTAINERS_ASSOCIATIVE_CONTAINER_BENCHMARKS_H
|