|
| 1 | +/* |
| 2 | + * Copyright 2020 Improbable Worlds Limited |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | +#include "benchmark_util.h" |
| 17 | +#include "logging.h" |
| 18 | +#include "phtree/phtree.h" |
| 19 | +#include "phtree/phtree_multimap.h" |
| 20 | +#include <benchmark/benchmark.h> |
| 21 | +#include <random> |
| 22 | + |
| 23 | +using namespace improbable; |
| 24 | +using namespace improbable::phtree; |
| 25 | +using namespace improbable::phtree::phbenchmark; |
| 26 | + |
| 27 | +/* |
| 28 | + * Benchmark for k-nearest-neighbour queries in multi-map implementations. |
| 29 | + */ |
| 30 | +namespace { |
| 31 | + |
| 32 | +const double GLOBAL_MAX = 10000; |
| 33 | +const dimension_t DIM = 3; |
| 34 | + |
| 35 | +enum Scenario { |
| 36 | + TREE_SET, |
| 37 | + PHTREE_MM, |
| 38 | + PHTREE_MM_STD, |
| 39 | + PHTREE2, |
| 40 | + TS_KD, |
| 41 | + TS_QT, |
| 42 | +}; |
| 43 | + |
| 44 | +using payload_t = int64_t; |
| 45 | +using payload2_t = uint32_t; |
| 46 | + |
| 47 | +using TestPoint = PhPointD<DIM>; |
| 48 | +using QueryBox = PhBoxD<DIM>; |
| 49 | +using BucketType = std::set<payload_t>; |
| 50 | + |
| 51 | +template <dimension_t DIM> |
| 52 | +using CONVERTER = ConverterIEEE<DIM>; |
| 53 | + |
| 54 | +template <Scenario SCENARIO, dimension_t DIM> |
| 55 | +using TestMap = typename std::conditional_t< |
| 56 | + SCENARIO == TREE_SET, |
| 57 | + PhTreeD<DIM, BucketType, CONVERTER<DIM>>, |
| 58 | + typename std::conditional_t< |
| 59 | + SCENARIO == PHTREE_MM, |
| 60 | + PhTreeMultiMap<DIM, payload_t, CONVERTER<DIM>, b_plus_tree_hash_set<payload_t>>, |
| 61 | +// typename std::conditional_t< |
| 62 | +// SCENARIO == PHTREE2, |
| 63 | +// PhTreeMultiMap2D<DIM, payload_t>, |
| 64 | + typename std::conditional_t< |
| 65 | + SCENARIO == PHTREE_MM_STD, |
| 66 | + PhTreeMultiMap<DIM, payload_t, CONVERTER<DIM>, BucketType>, |
| 67 | + void>>>; |
| 68 | + |
| 69 | +template <dimension_t DIM, Scenario SCENARIO> |
| 70 | +class IndexBenchmark { |
| 71 | + public: |
| 72 | + IndexBenchmark(benchmark::State& state, int knn_result_size_); |
| 73 | + |
| 74 | + void Benchmark(benchmark::State& state); |
| 75 | + |
| 76 | + private: |
| 77 | + void SetupWorld(benchmark::State& state); |
| 78 | + void QueryWorld(benchmark::State& state, TestPoint& center); |
| 79 | + void CreateQuery(TestPoint& center); |
| 80 | + |
| 81 | + const TestGenerator data_type_; |
| 82 | + const size_t num_entities_; |
| 83 | + const size_t knn_result_size_; |
| 84 | + |
| 85 | + TestMap<SCENARIO, DIM> tree_; |
| 86 | + std::default_random_engine random_engine_; |
| 87 | + std::uniform_real_distribution<> cube_distribution_; |
| 88 | + std::vector<TestPoint> points_; |
| 89 | +}; |
| 90 | + |
| 91 | +template <dimension_t DIM, Scenario SCENARIO> |
| 92 | +IndexBenchmark<DIM, SCENARIO>::IndexBenchmark(benchmark::State& state, int knn_result_size) |
| 93 | +: data_type_{static_cast<TestGenerator>(state.range(1))} |
| 94 | +, num_entities_(state.range(0)) |
| 95 | +, knn_result_size_(knn_result_size) |
| 96 | +, random_engine_{1} |
| 97 | +, cube_distribution_{0, GLOBAL_MAX} |
| 98 | +, points_(state.range(0)) { |
| 99 | + logging::SetupDefaultLogging(); |
| 100 | + SetupWorld(state); |
| 101 | +} |
| 102 | + |
| 103 | +template <dimension_t DIM, Scenario SCENARIO> |
| 104 | +void IndexBenchmark<DIM, SCENARIO>::Benchmark(benchmark::State& state) { |
| 105 | + for (auto _ : state) { |
| 106 | + state.PauseTiming(); |
| 107 | + TestPoint center; |
| 108 | + CreateQuery(center); |
| 109 | + state.ResumeTiming(); |
| 110 | + |
| 111 | + QueryWorld(state, center); |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +template < |
| 116 | + dimension_t DIM, |
| 117 | + Scenario SCENARIO, |
| 118 | + std::enable_if_t<(SCENARIO == Scenario::TREE_SET), int> = 0> |
| 119 | +void InsertEntries(TestMap<Scenario::TREE_SET, DIM>& tree, const std::vector<TestPoint>& points) { |
| 120 | + for (size_t i = 0; i < points.size(); ++i) { |
| 121 | + BucketType& bucket = tree.emplace(points[i]).first; |
| 122 | + bucket.emplace((payload_t)i); |
| 123 | + } |
| 124 | +} |
| 125 | + |
| 126 | +template <dimension_t DIM, Scenario SCN, std::enable_if_t<(SCN != Scenario::TREE_SET), int> = 0> |
| 127 | +void InsertEntries(TestMap<SCN, DIM>& tree, const std::vector<TestPoint>& points) { |
| 128 | + for (size_t i = 0; i < points.size(); ++i) { |
| 129 | + tree.emplace(points[i], (payload_t)i); |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +template <dimension_t DIM, Scenario SCN> |
| 134 | +size_t QueryAll(TestMap<SCN, DIM>& tree, const TestPoint& center, const size_t k) { |
| 135 | + size_t n = 0; |
| 136 | + for (auto q = tree.begin_knn_query(k, center, DistanceEuclidean<DIM>()); q != tree.end(); ++q) { |
| 137 | + ++n; |
| 138 | + } |
| 139 | + return n; |
| 140 | +} |
| 141 | + |
| 142 | +struct CounterTreeWithMap { |
| 143 | + void operator()(const TestPoint&, const BucketType& value) { |
| 144 | + for (auto& x : value) { |
| 145 | + (void)x; |
| 146 | + n_ += 1; |
| 147 | + } |
| 148 | + } |
| 149 | + size_t n_; |
| 150 | +}; |
| 151 | + |
| 152 | +struct CounterMultiMap { |
| 153 | + void operator()(const TestPoint&, const payload_t&) { |
| 154 | + n_ += 1; |
| 155 | + } |
| 156 | + size_t n_; |
| 157 | +}; |
| 158 | + |
| 159 | +template <dimension_t DIM, Scenario SCENARIO> |
| 160 | +void IndexBenchmark<DIM, SCENARIO>::SetupWorld(benchmark::State& state) { |
| 161 | + logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); |
| 162 | + CreatePointData<DIM>(points_, data_type_, num_entities_, 0, GLOBAL_MAX); |
| 163 | + InsertEntries<DIM, SCENARIO>(tree_, points_); |
| 164 | + |
| 165 | + state.counters["query_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); |
| 166 | + state.counters["result_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); |
| 167 | + state.counters["avg_result_count"] = benchmark::Counter(0, benchmark::Counter::kAvgIterations); |
| 168 | + logging::info("World setup complete."); |
| 169 | +} |
| 170 | + |
| 171 | +template <dimension_t DIM, Scenario SCENARIO> |
| 172 | +void IndexBenchmark<DIM, SCENARIO>::QueryWorld(benchmark::State& state, TestPoint& center) { |
| 173 | + size_t n = QueryAll<DIM, SCENARIO>(tree_, center, knn_result_size_); |
| 174 | + |
| 175 | + state.counters["query_rate"] += 1; |
| 176 | + state.counters["result_rate"] += n; |
| 177 | + state.counters["avg_result_count"] += n; |
| 178 | +} |
| 179 | + |
| 180 | +template <dimension_t DIM, Scenario SCENARIO> |
| 181 | +void IndexBenchmark<DIM, SCENARIO>::CreateQuery(TestPoint& center) { |
| 182 | + for (dimension_t d = 0; d < DIM; ++d) { |
| 183 | + center[d] = cube_distribution_(random_engine_) * GLOBAL_MAX; |
| 184 | + } |
| 185 | +} |
| 186 | + |
| 187 | +} // namespace |
| 188 | + |
| 189 | +// template <typename... Arguments> |
| 190 | +// void TinspinKDTree(benchmark::State& state, Arguments&&... arguments) { |
| 191 | +// IndexBenchmark<DIM, Scenario::TS_KD> benchmark{state, arguments...}; |
| 192 | +// benchmark.Benchmark(state); |
| 193 | +// } |
| 194 | +// |
| 195 | +// template <typename... Arguments> |
| 196 | +// void TinspinQuadtree(benchmark::State& state, Arguments&&... arguments) { |
| 197 | +// IndexBenchmark<DIM, Scenario::TS_QT> benchmark{state, arguments...}; |
| 198 | +// benchmark.Benchmark(state); |
| 199 | +// } |
| 200 | + |
| 201 | +template <typename... Arguments> |
| 202 | +void PhTree3D(benchmark::State& state, Arguments&&... arguments) { |
| 203 | + IndexBenchmark<DIM, Scenario::TREE_SET> benchmark{state, arguments...}; |
| 204 | + benchmark.Benchmark(state); |
| 205 | +} |
| 206 | + |
| 207 | +template <typename... Arguments> |
| 208 | +void PhTreeMM(benchmark::State& state, Arguments&&... arguments) { |
| 209 | + IndexBenchmark<DIM, Scenario::PHTREE_MM> benchmark{state, arguments...}; |
| 210 | + benchmark.Benchmark(state); |
| 211 | +} |
| 212 | + |
| 213 | +//template <typename... Arguments> |
| 214 | +//void PhTreeMM2(benchmark::State& state, Arguments&&... arguments) { |
| 215 | +// IndexBenchmark<DIM, Scenario::PHTREE2> benchmark{state, arguments...}; |
| 216 | +// benchmark.Benchmark(state); |
| 217 | +//} |
| 218 | + |
| 219 | +template <typename... Arguments> |
| 220 | +void PhTreeMMStdSet(benchmark::State& state, Arguments&&... arguments) { |
| 221 | + IndexBenchmark<DIM, Scenario::PHTREE_MM_STD> benchmark{state, arguments...}; |
| 222 | + benchmark.Benchmark(state); |
| 223 | +} |
| 224 | + |
| 225 | +// index type, scenario name, data_type, num_entities, query_result_size |
| 226 | + |
| 227 | +// PhTree multi-map 1.0 |
| 228 | +BENCHMARK_CAPTURE(PhTreeMM, KNN_1, 1) |
| 229 | + ->RangeMultiplier(10) |
| 230 | + ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) |
| 231 | + ->Unit(benchmark::kMillisecond); |
| 232 | + |
| 233 | +BENCHMARK_CAPTURE(PhTreeMM, KNN_10, 10) |
| 234 | + ->RangeMultiplier(10) |
| 235 | + ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) |
| 236 | + ->Unit(benchmark::kMillisecond); |
| 237 | + |
| 238 | +//// Multimap 2.0 |
| 239 | +//BENCHMARK_CAPTURE(PhTreeMM2, KNN_1, 1) |
| 240 | +// ->RangeMultiplier(10) |
| 241 | +// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) |
| 242 | +// ->Unit(benchmark::kMillisecond); |
| 243 | +// |
| 244 | +//BENCHMARK_CAPTURE(PhTreeMM2, KNN_10, 10) |
| 245 | +// ->RangeMultiplier(10) |
| 246 | +// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) |
| 247 | +// ->Unit(benchmark::kMillisecond); |
| 248 | + |
| 249 | +//// KD-tree |
| 250 | +// BENCHMARK_CAPTURE(TinspinKDTree, KNN_1, 1) |
| 251 | +// ->RangeMultiplier(10) |
| 252 | +// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) |
| 253 | +// ->Unit(benchmark::kMillisecond); |
| 254 | +// |
| 255 | +// BENCHMARK_CAPTURE(TinspinKDTree, KNN_10, 10) |
| 256 | +// ->RangeMultiplier(10) |
| 257 | +// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) |
| 258 | +// ->Unit(benchmark::kMillisecond); |
| 259 | +// |
| 260 | +//// Quadtree |
| 261 | +// BENCHMARK_CAPTURE(TinspinQuadtree, KNN_1, 1) |
| 262 | +// ->RangeMultiplier(10) |
| 263 | +// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) |
| 264 | +// ->Unit(benchmark::kMillisecond); |
| 265 | +// |
| 266 | +// BENCHMARK_CAPTURE(TinspinQuadtree, KNN_10, 10) |
| 267 | +// ->RangeMultiplier(10) |
| 268 | +// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) |
| 269 | +// ->Unit(benchmark::kMillisecond); |
| 270 | + |
| 271 | +// PhTree 3D with set |
| 272 | +BENCHMARK_CAPTURE(PhTree3D, KNN_1, 1) |
| 273 | + ->RangeMultiplier(10) |
| 274 | + ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) |
| 275 | + ->Unit(benchmark::kMillisecond); |
| 276 | + |
| 277 | +BENCHMARK_CAPTURE(PhTree3D, KNN_10, 10) |
| 278 | + ->RangeMultiplier(10) |
| 279 | + ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) |
| 280 | + ->Unit(benchmark::kMillisecond); |
| 281 | + |
| 282 | +BENCHMARK_MAIN(); |
0 commit comments