| // Copyright 2018 The Abseil Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include <stdint.h> | |
| #include <algorithm> | |
| #include <array> | |
| #include <cstddef> | |
| #include <functional> | |
| #include <map> | |
| #include <memory> | |
| #include <numeric> | |
| #include <random> | |
| #include <set> | |
| #include <string> | |
| #include <type_traits> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/algorithm/container.h" | |
| #include "absl/base/config.h" | |
| #include "absl/base/internal/raw_logging.h" | |
| #include "absl/container/btree_map.h" | |
| #include "absl/container/btree_set.h" | |
| #include "absl/container/btree_test.h" | |
| #include "absl/container/flat_hash_map.h" | |
| #include "absl/container/flat_hash_set.h" | |
| #include "absl/container/internal/hashtable_debug.h" | |
| #include "absl/hash/hash.h" | |
| #include "absl/log/log.h" | |
| #include "absl/memory/memory.h" | |
| #include "absl/random/random.h" | |
| #include "absl/strings/cord.h" | |
| #include "absl/strings/str_format.h" | |
| #include "absl/time/time.h" | |
| #include "benchmark/benchmark.h" | |
| namespace absl { | |
| ABSL_NAMESPACE_BEGIN | |
| namespace container_internal { | |
| namespace { | |
| constexpr size_t kBenchmarkValues = 1 << 20; | |
| // How many times we add and remove sub-batches in one batch of *AddRem | |
| // benchmarks. | |
| constexpr size_t kAddRemBatchSize = 1 << 2; | |
| // Generates n values in the range [0, 4 * n]. | |
| template <typename V> | |
| std::vector<V> GenerateValues(int n) { | |
| constexpr int kSeed = 23; | |
| return GenerateValuesWithSeed<V>(n, 4 * n, kSeed); | |
| } | |
| // Benchmark insertion of values into a container. | |
| template <typename T> | |
| void BM_InsertImpl(benchmark::State& state, bool sorted) { | |
| using V = typename remove_pair_const<typename T::value_type>::type; | |
| typename KeyOfValue<typename T::key_type, V>::type key_of_value; | |
| std::vector<V> values = GenerateValues<V>(kBenchmarkValues); | |
| if (sorted) { | |
| std::sort(values.begin(), values.end()); | |
| } | |
| T container(values.begin(), values.end()); | |
| // Remove and re-insert 10% of the keys per batch. | |
| const int batch_size = (kBenchmarkValues + 9) / 10; | |
| while (state.KeepRunningBatch(batch_size)) { | |
| state.PauseTiming(); | |
| const auto i = static_cast<int>(state.iterations()); | |
| for (int j = i; j < i + batch_size; j++) { | |
| int x = j % kBenchmarkValues; | |
| container.erase(key_of_value(values[x])); | |
| } | |
| state.ResumeTiming(); | |
| for (int j = i; j < i + batch_size; j++) { | |
| int x = j % kBenchmarkValues; | |
| container.insert(values[x]); | |
| } | |
| } | |
| } | |
| template <typename T> | |
| void BM_Insert(benchmark::State& state) { | |
| BM_InsertImpl<T>(state, false); | |
| } | |
| template <typename T> | |
| void BM_InsertSorted(benchmark::State& state) { | |
| BM_InsertImpl<T>(state, true); | |
| } | |
| // Benchmark inserting the first few elements in a container. In b-tree, this is | |
| // when the root node grows. | |
| template <typename T> | |
| void BM_InsertSmall(benchmark::State& state) { | |
| using V = typename remove_pair_const<typename T::value_type>::type; | |
| const int kSize = 8; | |
| std::vector<V> values = GenerateValues<V>(kSize); | |
| T container; | |
| while (state.KeepRunningBatch(kSize)) { | |
| for (int i = 0; i < kSize; ++i) { | |
| benchmark::DoNotOptimize(container.insert(values[i])); | |
| } | |
| state.PauseTiming(); | |
| // Do not measure the time it takes to clear the container. | |
| container.clear(); | |
| state.ResumeTiming(); | |
| } | |
| } | |
| template <typename T> | |
| void BM_LookupImpl(benchmark::State& state, bool sorted) { | |
| using V = typename remove_pair_const<typename T::value_type>::type; | |
| typename KeyOfValue<typename T::key_type, V>::type key_of_value; | |
| std::vector<V> values = GenerateValues<V>(kBenchmarkValues); | |
| if (sorted) { | |
| std::sort(values.begin(), values.end()); | |
| } | |
| T container(values.begin(), values.end()); | |
| while (state.KeepRunning()) { | |
| int idx = state.iterations() % kBenchmarkValues; | |
| benchmark::DoNotOptimize(container.find(key_of_value(values[idx]))); | |
| } | |
| } | |
| // Benchmark lookup of values in a container. | |
| template <typename T> | |
| void BM_Lookup(benchmark::State& state) { | |
| BM_LookupImpl<T>(state, false); | |
| } | |
| // Benchmark lookup of values in a full container, meaning that values | |
| // are inserted in-order to take advantage of biased insertion, which | |
| // yields a full tree. | |
| template <typename T> | |
| void BM_FullLookup(benchmark::State& state) { | |
| BM_LookupImpl<T>(state, true); | |
| } | |
| // Benchmark erasing values from a container. | |
| template <typename T> | |
| void BM_Erase(benchmark::State& state) { | |
| using V = typename remove_pair_const<typename T::value_type>::type; | |
| typename KeyOfValue<typename T::key_type, V>::type key_of_value; | |
| std::vector<V> values = GenerateValues<V>(kBenchmarkValues); | |
| T container(values.begin(), values.end()); | |
| // Remove and re-insert 10% of the keys per batch. | |
| const int batch_size = (kBenchmarkValues + 9) / 10; | |
| while (state.KeepRunningBatch(batch_size)) { | |
| const int i = state.iterations(); | |
| for (int j = i; j < i + batch_size; j++) { | |
| int x = j % kBenchmarkValues; | |
| container.erase(key_of_value(values[x])); | |
| } | |
| state.PauseTiming(); | |
| for (int j = i; j < i + batch_size; j++) { | |
| int x = j % kBenchmarkValues; | |
| container.insert(values[x]); | |
| } | |
| state.ResumeTiming(); | |
| } | |
| } | |
| // Benchmark erasing multiple values from a container. | |
| template <typename T> | |
| void BM_EraseRange(benchmark::State& state) { | |
| using V = typename remove_pair_const<typename T::value_type>::type; | |
| typename KeyOfValue<typename T::key_type, V>::type key_of_value; | |
| std::vector<V> values = GenerateValues<V>(kBenchmarkValues); | |
| T container(values.begin(), values.end()); | |
| // Remove and re-insert 10% of the keys per batch. | |
| const int batch_size = (kBenchmarkValues + 9) / 10; | |
| while (state.KeepRunningBatch(batch_size)) { | |
| const int i = state.iterations(); | |
| const int start_index = i % kBenchmarkValues; | |
| state.PauseTiming(); | |
| { | |
| std::vector<V> removed; | |
| removed.reserve(batch_size); | |
| auto itr = container.find(key_of_value(values[start_index])); | |
| auto start = itr; | |
| for (int j = 0; j < batch_size; j++) { | |
| if (itr == container.end()) { | |
| state.ResumeTiming(); | |
| container.erase(start, itr); | |
| state.PauseTiming(); | |
| itr = container.begin(); | |
| start = itr; | |
| } | |
| removed.push_back(*itr++); | |
| } | |
| state.ResumeTiming(); | |
| container.erase(start, itr); | |
| state.PauseTiming(); | |
| container.insert(removed.begin(), removed.end()); | |
| } | |
| state.ResumeTiming(); | |
| } | |
| } | |
| // Predicate that erases every other element. We can't use a lambda because | |
| // C++11 doesn't support generic lambdas. | |
| // TODO(b/207389011): consider adding benchmarks that remove different fractions | |
| // of keys (e.g. 10%, 90%). | |
| struct EraseIfPred { | |
| uint64_t i = 0; | |
| template <typename T> | |
| bool operator()(const T&) { | |
| return ++i % 2; | |
| } | |
| }; | |
| // Benchmark erasing multiple values from a container with a predicate. | |
| template <typename T> | |
| void BM_EraseIf(benchmark::State& state) { | |
| using V = typename remove_pair_const<typename T::value_type>::type; | |
| std::vector<V> values = GenerateValues<V>(kBenchmarkValues); | |
| // Removes half of the keys per batch. | |
| const int batch_size = (kBenchmarkValues + 1) / 2; | |
| EraseIfPred pred; | |
| while (state.KeepRunningBatch(batch_size)) { | |
| state.PauseTiming(); | |
| { | |
| T container(values.begin(), values.end()); | |
| state.ResumeTiming(); | |
| erase_if(container, pred); | |
| benchmark::DoNotOptimize(container); | |
| state.PauseTiming(); | |
| } | |
| state.ResumeTiming(); | |
| } | |
| } | |
| // Benchmark steady-state insert (into first half of range) and remove (from | |
| // second half of range), treating the container approximately like a queue with | |
| // log-time access for all elements. This benchmark does not test the case where | |
| // insertion and removal happen in the same region of the tree. This benchmark | |
| // counts two value constructors. | |
| template <typename T> | |
| void BM_QueueAddRem(benchmark::State& state) { | |
| using V = typename remove_pair_const<typename T::value_type>::type; | |
| typename KeyOfValue<typename T::key_type, V>::type key_of_value; | |
| ABSL_RAW_CHECK(kBenchmarkValues % 2 == 0, "for performance"); | |
| T container; | |
| const size_t half = kBenchmarkValues / 2; | |
| std::vector<int> remove_keys(half); | |
| std::vector<int> add_keys(half); | |
| // We want to do the exact same work repeatedly, and the benchmark can end | |
| // after a different number of iterations depending on the speed of the | |
| // individual run so we use a large batch size here and ensure that we do | |
| // deterministic work every batch. | |
| while (state.KeepRunningBatch(half * kAddRemBatchSize)) { | |
| state.PauseTiming(); | |
| container.clear(); | |
| for (size_t i = 0; i < half; ++i) { | |
| remove_keys[i] = i; | |
| add_keys[i] = i; | |
| } | |
| constexpr int kSeed = 5; | |
| std::mt19937_64 rand(kSeed); | |
| std::shuffle(remove_keys.begin(), remove_keys.end(), rand); | |
| std::shuffle(add_keys.begin(), add_keys.end(), rand); | |
| // Note needs lazy generation of values. | |
| Generator<V> g(kBenchmarkValues * kAddRemBatchSize); | |
| for (size_t i = 0; i < half; ++i) { | |
| container.insert(g(add_keys[i])); | |
| container.insert(g(half + remove_keys[i])); | |
| } | |
| // There are three parts each of size "half": | |
| // 1 is being deleted from [offset - half, offset) | |
| // 2 is standing [offset, offset + half) | |
| // 3 is being inserted into [offset + half, offset + 2 * half) | |
| size_t offset = 0; | |
| for (size_t i = 0; i < kAddRemBatchSize; ++i) { | |
| std::shuffle(remove_keys.begin(), remove_keys.end(), rand); | |
| std::shuffle(add_keys.begin(), add_keys.end(), rand); | |
| offset += half; | |
| state.ResumeTiming(); | |
| for (size_t idx = 0; idx < half; ++idx) { | |
| container.erase(key_of_value(g(offset - half + remove_keys[idx]))); | |
| container.insert(g(offset + half + add_keys[idx])); | |
| } | |
| state.PauseTiming(); | |
| } | |
| state.ResumeTiming(); | |
| } | |
| } | |
| // Mixed insertion and deletion in the same range using pre-constructed values. | |
| template <typename T> | |
| void BM_MixedAddRem(benchmark::State& state) { | |
| using V = typename remove_pair_const<typename T::value_type>::type; | |
| typename KeyOfValue<typename T::key_type, V>::type key_of_value; | |
| ABSL_RAW_CHECK(kBenchmarkValues % 2 == 0, "for performance"); | |
| T container; | |
| // Create two random shuffles | |
| std::vector<int> remove_keys(kBenchmarkValues); | |
| std::vector<int> add_keys(kBenchmarkValues); | |
| // We want to do the exact same work repeatedly, and the benchmark can end | |
| // after a different number of iterations depending on the speed of the | |
| // individual run so we use a large batch size here and ensure that we do | |
| // deterministic work every batch. | |
| while (state.KeepRunningBatch(kBenchmarkValues * kAddRemBatchSize)) { | |
| state.PauseTiming(); | |
| container.clear(); | |
| constexpr int kSeed = 7; | |
| std::mt19937_64 rand(kSeed); | |
| std::vector<V> values = GenerateValues<V>(kBenchmarkValues * 2); | |
| // Insert the first half of the values (already in random order) | |
| container.insert(values.begin(), values.begin() + kBenchmarkValues); | |
| // Insert the first half of the values (already in random order) | |
| for (size_t i = 0; i < kBenchmarkValues; ++i) { | |
| // remove_keys and add_keys will be swapped before each round, | |
| // therefore fill add_keys here w/ the keys being inserted, so | |
| // they'll be the first to be removed. | |
| remove_keys[i] = i + kBenchmarkValues; | |
| add_keys[i] = i; | |
| } | |
| for (size_t i = 0; i < kAddRemBatchSize; ++i) { | |
| remove_keys.swap(add_keys); | |
| std::shuffle(remove_keys.begin(), remove_keys.end(), rand); | |
| std::shuffle(add_keys.begin(), add_keys.end(), rand); | |
| state.ResumeTiming(); | |
| for (size_t idx = 0; idx < kBenchmarkValues; ++idx) { | |
| container.erase(key_of_value(values[remove_keys[idx]])); | |
| container.insert(values[add_keys[idx]]); | |
| } | |
| state.PauseTiming(); | |
| } | |
| state.ResumeTiming(); | |
| } | |
| } | |
| // Insertion at end, removal from the beginning. This benchmark | |
| // counts two value constructors. | |
| // TODO(ezb): we could add a GenerateNext version of generator that could reduce | |
| // noise for string-like types. | |
| template <typename T> | |
| void BM_Fifo(benchmark::State& state) { | |
| using V = typename remove_pair_const<typename T::value_type>::type; | |
| T container; | |
| // Need lazy generation of values as state.max_iterations is large. | |
| Generator<V> g(kBenchmarkValues + state.max_iterations); | |
| for (int i = 0; i < kBenchmarkValues; i++) { | |
| container.insert(g(i)); | |
| } | |
| while (state.KeepRunning()) { | |
| container.erase(container.begin()); | |
| container.insert(container.end(), g(state.iterations() + kBenchmarkValues)); | |
| } | |
| } | |
| // Iteration (forward) through the tree | |
| template <typename T> | |
| void BM_FwdIter(benchmark::State& state) { | |
| using V = typename remove_pair_const<typename T::value_type>::type; | |
| std::vector<V> values = GenerateValues<V>(kBenchmarkValues); | |
| T container(values.begin(), values.end()); | |
| auto iter = container.end(); | |
| while (state.KeepRunning()) { | |
| if (iter == container.end()) iter = container.begin(); | |
| auto *v = &(*iter); | |
| benchmark::DoNotOptimize(v); | |
| benchmark::DoNotOptimize(++iter); | |
| } | |
| } | |
| // Benchmark random range-construction of a container. | |
| template <typename T> | |
| void BM_RangeConstructionImpl(benchmark::State& state, bool sorted) { | |
| using V = typename remove_pair_const<typename T::value_type>::type; | |
| std::vector<V> values = GenerateValues<V>(kBenchmarkValues); | |
| if (sorted) { | |
| std::sort(values.begin(), values.end()); | |
| } | |
| { | |
| T container(values.begin(), values.end()); | |
| } | |
| while (state.KeepRunning()) { | |
| T container(values.begin(), values.end()); | |
| benchmark::DoNotOptimize(container); | |
| } | |
| } | |
| template <typename T> | |
| void BM_InsertRangeRandom(benchmark::State& state) { | |
| BM_RangeConstructionImpl<T>(state, false); | |
| } | |
| template <typename T> | |
| void BM_InsertRangeSorted(benchmark::State& state) { | |
| BM_RangeConstructionImpl<T>(state, true); | |
| } | |
| #define STL_ORDERED_TYPES(value) \ | |
| using stl_set_##value = std::set<value>; \ | |
| using stl_map_##value = std::map<value, intptr_t>; \ | |
| using stl_multiset_##value = std::multiset<value>; \ | |
| using stl_multimap_##value = std::multimap<value, intptr_t> | |
| using StdString = std::string; | |
| STL_ORDERED_TYPES(int32_t); | |
| STL_ORDERED_TYPES(int64_t); | |
| STL_ORDERED_TYPES(StdString); | |
| STL_ORDERED_TYPES(Cord); | |
| STL_ORDERED_TYPES(Time); | |
| #define STL_UNORDERED_TYPES(value) \ | |
| using stl_unordered_set_##value = std::unordered_set<value>; \ | |
| using stl_unordered_map_##value = std::unordered_map<value, intptr_t>; \ | |
| using flat_hash_set_##value = flat_hash_set<value>; \ | |
| using flat_hash_map_##value = flat_hash_map<value, intptr_t>; \ | |
| using stl_unordered_multiset_##value = std::unordered_multiset<value>; \ | |
| using stl_unordered_multimap_##value = \ | |
| std::unordered_multimap<value, intptr_t> | |
| #define STL_UNORDERED_TYPES_CUSTOM_HASH(value, hash) \ | |
| using stl_unordered_set_##value = std::unordered_set<value, hash>; \ | |
| using stl_unordered_map_##value = std::unordered_map<value, intptr_t, hash>; \ | |
| using flat_hash_set_##value = flat_hash_set<value, hash>; \ | |
| using flat_hash_map_##value = flat_hash_map<value, intptr_t, hash>; \ | |
| using stl_unordered_multiset_##value = std::unordered_multiset<value, hash>; \ | |
| using stl_unordered_multimap_##value = \ | |
| std::unordered_multimap<value, intptr_t, hash> | |
| STL_UNORDERED_TYPES_CUSTOM_HASH(Cord, absl::Hash<absl::Cord>); | |
| STL_UNORDERED_TYPES(int32_t); | |
| STL_UNORDERED_TYPES(int64_t); | |
| STL_UNORDERED_TYPES(StdString); | |
| STL_UNORDERED_TYPES_CUSTOM_HASH(Time, absl::Hash<absl::Time>); | |
| #define BTREE_TYPES(value) \ | |
| using btree_256_set_##value = \ | |
| btree_set<value, std::less<value>, std::allocator<value>>; \ | |
| using btree_256_map_##value = \ | |
| btree_map<value, intptr_t, std::less<value>, \ | |
| std::allocator<std::pair<const value, intptr_t>>>; \ | |
| using btree_256_multiset_##value = \ | |
| btree_multiset<value, std::less<value>, std::allocator<value>>; \ | |
| using btree_256_multimap_##value = \ | |
| btree_multimap<value, intptr_t, std::less<value>, \ | |
| std::allocator<std::pair<const value, intptr_t>>> | |
| BTREE_TYPES(int32_t); | |
| BTREE_TYPES(int64_t); | |
| BTREE_TYPES(StdString); | |
| BTREE_TYPES(Cord); | |
| BTREE_TYPES(Time); | |
| #define MY_BENCHMARK4(type, func) \ | |
| void BM_##type##_##func(benchmark::State& state) { BM_##func<type>(state); } \ | |
| BENCHMARK(BM_##type##_##func) | |
| #define MY_BENCHMARK3_STL(type) \ | |
| MY_BENCHMARK4(type, Insert); \ | |
| MY_BENCHMARK4(type, InsertSorted); \ | |
| MY_BENCHMARK4(type, InsertSmall); \ | |
| MY_BENCHMARK4(type, Lookup); \ | |
| MY_BENCHMARK4(type, FullLookup); \ | |
| MY_BENCHMARK4(type, Erase); \ | |
| MY_BENCHMARK4(type, EraseRange); \ | |
| MY_BENCHMARK4(type, QueueAddRem); \ | |
| MY_BENCHMARK4(type, MixedAddRem); \ | |
| MY_BENCHMARK4(type, Fifo); \ | |
| MY_BENCHMARK4(type, FwdIter); \ | |
| MY_BENCHMARK4(type, InsertRangeRandom); \ | |
| MY_BENCHMARK4(type, InsertRangeSorted) | |
| #define MY_BENCHMARK3(type) \ | |
| MY_BENCHMARK4(type, EraseIf); \ | |
| MY_BENCHMARK3_STL(type) | |
| #define MY_BENCHMARK2_SUPPORTS_MULTI_ONLY(type) \ | |
| MY_BENCHMARK3_STL(stl_##type); \ | |
| MY_BENCHMARK3_STL(stl_unordered_##type); \ | |
| MY_BENCHMARK3(btree_256_##type) | |
| #define MY_BENCHMARK2(type) \ | |
| MY_BENCHMARK2_SUPPORTS_MULTI_ONLY(type); \ | |
| MY_BENCHMARK3(flat_hash_##type) | |
| // Define MULTI_TESTING to see benchmarks for multi-containers also. | |
| // | |
| // You can use --copt=-DMULTI_TESTING. | |
| #ifdef MULTI_TESTING | |
| #define MY_BENCHMARK(type) \ | |
| MY_BENCHMARK2(set_##type); \ | |
| MY_BENCHMARK2(map_##type); \ | |
| MY_BENCHMARK2_SUPPORTS_MULTI_ONLY(multiset_##type); \ | |
| MY_BENCHMARK2_SUPPORTS_MULTI_ONLY(multimap_##type) | |
| #else | |
| #define MY_BENCHMARK(type) \ | |
| MY_BENCHMARK2(set_##type); \ | |
| MY_BENCHMARK2(map_##type) | |
| #endif | |
| MY_BENCHMARK(int32_t); | |
| MY_BENCHMARK(int64_t); | |
| MY_BENCHMARK(StdString); | |
| MY_BENCHMARK(Cord); | |
| MY_BENCHMARK(Time); | |
| // Define a type whose size and cost of moving are independently customizable. | |
| // When sizeof(value_type) increases, we expect btree to no longer have as much | |
| // cache-locality advantage over STL. When cost of moving increases, we expect | |
| // btree to actually do more work than STL because it has to move values around | |
| // and STL doesn't have to. | |
| template <int Size, int Copies> | |
| struct BigType { | |
| BigType() : BigType(0) {} | |
| explicit BigType(int x) { std::iota(values.begin(), values.end(), x); } | |
| void Copy(const BigType& other) { | |
| for (int i = 0; i < Size && i < Copies; ++i) values[i] = other.values[i]; | |
| // If Copies > Size, do extra copies. | |
| for (int i = Size, idx = 0; i < Copies; ++i) { | |
| int64_t tmp = other.values[idx]; | |
| benchmark::DoNotOptimize(tmp); | |
| idx = idx + 1 == Size ? 0 : idx + 1; | |
| } | |
| } | |
| BigType(const BigType& other) { Copy(other); } | |
| BigType& operator=(const BigType& other) { | |
| Copy(other); | |
| return *this; | |
| } | |
| // Compare only the first Copies elements if Copies is less than Size. | |
| bool operator<(const BigType& other) const { | |
| return std::lexicographical_compare( | |
| values.begin(), values.begin() + std::min(Size, Copies), | |
| other.values.begin(), other.values.begin() + std::min(Size, Copies)); | |
| } | |
| bool operator==(const BigType& other) const { | |
| return std::equal(values.begin(), values.begin() + std::min(Size, Copies), | |
| other.values.begin()); | |
| } | |
| // Support absl::Hash. | |
| template <typename State> | |
| friend State AbslHashValue(State h, const BigType& b) { | |
| for (int i = 0; i < Size && i < Copies; ++i) | |
| h = State::combine(std::move(h), b.values[i]); | |
| return h; | |
| } | |
| std::array<int64_t, Size> values; | |
| }; | |
| #define BIG_TYPE_BENCHMARKS(SIZE, COPIES) \ | |
| using stl_set_size##SIZE##copies##COPIES = std::set<BigType<SIZE, COPIES>>; \ | |
| using stl_map_size##SIZE##copies##COPIES = \ | |
| std::map<BigType<SIZE, COPIES>, intptr_t>; \ | |
| using stl_multiset_size##SIZE##copies##COPIES = \ | |
| std::multiset<BigType<SIZE, COPIES>>; \ | |
| using stl_multimap_size##SIZE##copies##COPIES = \ | |
| std::multimap<BigType<SIZE, COPIES>, intptr_t>; \ | |
| using stl_unordered_set_size##SIZE##copies##COPIES = \ | |
| std::unordered_set<BigType<SIZE, COPIES>, \ | |
| absl::Hash<BigType<SIZE, COPIES>>>; \ | |
| using stl_unordered_map_size##SIZE##copies##COPIES = \ | |
| std::unordered_map<BigType<SIZE, COPIES>, intptr_t, \ | |
| absl::Hash<BigType<SIZE, COPIES>>>; \ | |
| using flat_hash_set_size##SIZE##copies##COPIES = \ | |
| flat_hash_set<BigType<SIZE, COPIES>>; \ | |
| using flat_hash_map_size##SIZE##copies##COPIES = \ | |
| flat_hash_map<BigType<SIZE, COPIES>, intptr_t>; \ | |
| using stl_unordered_multiset_size##SIZE##copies##COPIES = \ | |
| std::unordered_multiset<BigType<SIZE, COPIES>, \ | |
| absl::Hash<BigType<SIZE, COPIES>>>; \ | |
| using stl_unordered_multimap_size##SIZE##copies##COPIES = \ | |
| std::unordered_multimap<BigType<SIZE, COPIES>, intptr_t, \ | |
| absl::Hash<BigType<SIZE, COPIES>>>; \ | |
| using btree_256_set_size##SIZE##copies##COPIES = \ | |
| btree_set<BigType<SIZE, COPIES>>; \ | |
| using btree_256_map_size##SIZE##copies##COPIES = \ | |
| btree_map<BigType<SIZE, COPIES>, intptr_t>; \ | |
| using btree_256_multiset_size##SIZE##copies##COPIES = \ | |
| btree_multiset<BigType<SIZE, COPIES>>; \ | |
| using btree_256_multimap_size##SIZE##copies##COPIES = \ | |
| btree_multimap<BigType<SIZE, COPIES>, intptr_t>; \ | |
| MY_BENCHMARK(size##SIZE##copies##COPIES) | |
| // Define BIG_TYPE_TESTING to see benchmarks for more big types. | |
| // | |
| // You can use --copt=-DBIG_TYPE_TESTING. | |
| #ifndef NODESIZE_TESTING | |
| #ifdef BIG_TYPE_TESTING | |
| BIG_TYPE_BENCHMARKS(1, 4); | |
| BIG_TYPE_BENCHMARKS(4, 1); | |
| BIG_TYPE_BENCHMARKS(4, 4); | |
| BIG_TYPE_BENCHMARKS(1, 8); | |
| BIG_TYPE_BENCHMARKS(8, 1); | |
| BIG_TYPE_BENCHMARKS(8, 8); | |
| BIG_TYPE_BENCHMARKS(1, 16); | |
| BIG_TYPE_BENCHMARKS(16, 1); | |
| BIG_TYPE_BENCHMARKS(16, 16); | |
| BIG_TYPE_BENCHMARKS(1, 32); | |
| BIG_TYPE_BENCHMARKS(32, 1); | |
| BIG_TYPE_BENCHMARKS(32, 32); | |
| #else | |
| BIG_TYPE_BENCHMARKS(32, 32); | |
| #endif | |
| #endif | |
| // Benchmark using unique_ptrs to large value types. In order to be able to use | |
| // the same benchmark code as the other types, use a type that holds a | |
| // unique_ptr and has a copy constructor. | |
| template <int Size> | |
| struct BigTypePtr { | |
| BigTypePtr() : BigTypePtr(0) {} | |
| explicit BigTypePtr(int x) { ptr = std::make_unique<BigType<Size, Size>>(x); } | |
| BigTypePtr(const BigTypePtr& other) { | |
| ptr = std::make_unique<BigType<Size, Size>>(*other.ptr); | |
| } | |
| BigTypePtr(BigTypePtr&& other) noexcept = default; | |
| BigTypePtr& operator=(const BigTypePtr& other) { | |
| ptr = std::make_unique<BigType<Size, Size>>(*other.ptr); | |
| } | |
| BigTypePtr& operator=(BigTypePtr&& other) noexcept = default; | |
| bool operator<(const BigTypePtr& other) const { return *ptr < *other.ptr; } | |
| bool operator==(const BigTypePtr& other) const { return *ptr == *other.ptr; } | |
| std::unique_ptr<BigType<Size, Size>> ptr; | |
| }; | |
| template <int Size> | |
| double ContainerInfo(const btree_set<BigTypePtr<Size>>& b) { | |
| const double bytes_used = | |
| b.bytes_used() + b.size() * sizeof(BigType<Size, Size>); | |
| const double bytes_per_value = bytes_used / b.size(); | |
| BtreeContainerInfoLog(b, bytes_used, bytes_per_value); | |
| return bytes_per_value; | |
| } | |
| template <int Size> | |
| double ContainerInfo(const btree_map<int, BigTypePtr<Size>>& b) { | |
| const double bytes_used = | |
| b.bytes_used() + b.size() * sizeof(BigType<Size, Size>); | |
| const double bytes_per_value = bytes_used / b.size(); | |
| BtreeContainerInfoLog(b, bytes_used, bytes_per_value); | |
| return bytes_per_value; | |
| } | |
| #define BIG_TYPE_PTR_BENCHMARKS(SIZE) \ | |
| using stl_set_size##SIZE##copies##SIZE##ptr = std::set<BigType<SIZE, SIZE>>; \ | |
| using stl_map_size##SIZE##copies##SIZE##ptr = \ | |
| std::map<int, BigType<SIZE, SIZE>>; \ | |
| using stl_unordered_set_size##SIZE##copies##SIZE##ptr = \ | |
| std::unordered_set<BigType<SIZE, SIZE>, \ | |
| absl::Hash<BigType<SIZE, SIZE>>>; \ | |
| using stl_unordered_map_size##SIZE##copies##SIZE##ptr = \ | |
| std::unordered_map<int, BigType<SIZE, SIZE>>; \ | |
| using flat_hash_set_size##SIZE##copies##SIZE##ptr = \ | |
| flat_hash_set<BigType<SIZE, SIZE>>; \ | |
| using flat_hash_map_size##SIZE##copies##SIZE##ptr = \ | |
| flat_hash_map<int, BigTypePtr<SIZE>>; \ | |
| using btree_256_set_size##SIZE##copies##SIZE##ptr = \ | |
| btree_set<BigTypePtr<SIZE>>; \ | |
| using btree_256_map_size##SIZE##copies##SIZE##ptr = \ | |
| btree_map<int, BigTypePtr<SIZE>>; \ | |
| MY_BENCHMARK3_STL(stl_set_size##SIZE##copies##SIZE##ptr); \ | |
| MY_BENCHMARK3_STL(stl_unordered_set_size##SIZE##copies##SIZE##ptr); \ | |
| MY_BENCHMARK3(flat_hash_set_size##SIZE##copies##SIZE##ptr); \ | |
| MY_BENCHMARK3(btree_256_set_size##SIZE##copies##SIZE##ptr); \ | |
| MY_BENCHMARK3_STL(stl_map_size##SIZE##copies##SIZE##ptr); \ | |
| MY_BENCHMARK3_STL(stl_unordered_map_size##SIZE##copies##SIZE##ptr); \ | |
| MY_BENCHMARK3(flat_hash_map_size##SIZE##copies##SIZE##ptr); \ | |
| MY_BENCHMARK3(btree_256_map_size##SIZE##copies##SIZE##ptr) | |
| BIG_TYPE_PTR_BENCHMARKS(32); | |
| void BM_BtreeSet_IteratorDifference(benchmark::State& state) { | |
| absl::InsecureBitGen bitgen; | |
| std::vector<int> vec; | |
| // Randomize the set's insertion order so the nodes aren't all full. | |
| vec.reserve(state.range(0)); | |
| for (int i = 0; i < state.range(0); ++i) vec.push_back(i); | |
| absl::c_shuffle(vec, bitgen); | |
| absl::btree_set<int> set; | |
| for (int i : vec) set.insert(i); | |
| size_t distance = absl::Uniform(bitgen, 0u, set.size()); | |
| while (state.KeepRunningBatch(distance)) { | |
| size_t end = absl::Uniform(bitgen, distance, set.size()); | |
| size_t begin = end - distance; | |
| benchmark::DoNotOptimize(set.find(static_cast<int>(end)) - | |
| set.find(static_cast<int>(begin))); | |
| distance = absl::Uniform(bitgen, 0u, set.size()); | |
| } | |
| } | |
| BENCHMARK(BM_BtreeSet_IteratorDifference)->Range(1 << 10, 1 << 20); | |
| void BM_BtreeSet_IteratorAddition(benchmark::State& state) { | |
| absl::InsecureBitGen bitgen; | |
| std::vector<int> vec; | |
| // Randomize the set's insertion order so the nodes aren't all full. | |
| vec.reserve(static_cast<size_t>(state.range(0))); | |
| for (int i = 0; i < state.range(0); ++i) vec.push_back(i); | |
| absl::c_shuffle(vec, bitgen); | |
| absl::btree_set<int> set; | |
| for (int i : vec) set.insert(i); | |
| size_t distance = absl::Uniform(bitgen, 0u, set.size()); | |
| while (state.KeepRunningBatch(distance)) { | |
| // Let the increment go all the way to the `end` iterator. | |
| const size_t begin = | |
| absl::Uniform(absl::IntervalClosed, bitgen, 0u, set.size() - distance); | |
| auto it = set.find(static_cast<int>(begin)); | |
| benchmark::DoNotOptimize(it += static_cast<int>(distance)); | |
| distance = absl::Uniform(bitgen, 0u, set.size()); | |
| } | |
| } | |
| BENCHMARK(BM_BtreeSet_IteratorAddition)->Range(1 << 10, 1 << 20); | |
| void BM_BtreeSet_IteratorSubtraction(benchmark::State& state) { | |
| absl::InsecureBitGen bitgen; | |
| std::vector<int> vec; | |
| // Randomize the set's insertion order so the nodes aren't all full. | |
| vec.reserve(static_cast<size_t>(state.range(0))); | |
| for (int i = 0; i < state.range(0); ++i) vec.push_back(i); | |
| absl::c_shuffle(vec, bitgen); | |
| absl::btree_set<int> set; | |
| for (int i : vec) set.insert(i); | |
| size_t distance = absl::Uniform(bitgen, 0u, set.size()); | |
| while (state.KeepRunningBatch(distance)) { | |
| size_t end = absl::Uniform(bitgen, distance, set.size()); | |
| auto it = set.find(static_cast<int>(end)); | |
| benchmark::DoNotOptimize(it -= static_cast<int>(distance)); | |
| distance = absl::Uniform(bitgen, 0u, set.size()); | |
| } | |
| } | |
| BENCHMARK(BM_BtreeSet_IteratorSubtraction)->Range(1 << 10, 1 << 20); | |
| } // namespace | |
| } // namespace container_internal | |
| ABSL_NAMESPACE_END | |
| } // namespace absl |