Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
shortest_paths_benchmarks.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#include <cstdint>
15#include <memory>
16#include <numeric>
17#include <random>
18#include <utility>
19#include <vector>
20
21#include "absl/memory/memory.h"
22#include "absl/random/distributions.h"
23#include "benchmark/benchmark.h"
24#include "gtest/gtest.h"
25#include "isp/fiber/auto_design/utils/parallelizer.h"
26#include "ortools/base/gmock.h"
27#include "ortools/base/threadlocal.h"
30#include "ortools/graph/test_util.h"
31
32namespace operations_research {
33namespace {
34
35using Graph = StaticGraph<>;
36
37enum Implementation {
38 BOUNDED_DIJKSTRA = 1,
39 SHORTEST_PATHS = 2,
40};
41
42template <Implementation implementation>
43std::vector<std::vector<uint32_t>> ManyToManyShortestPaths(
44 const Graph& graph, const std::vector<uint32_t>& arc_costs,
45 const std::vector<int>& srcs, const std::vector<int>& dsts,
46 int num_threads) {
47 LOG(FATAL) << "Default implementation of ManyToManyShortestPaths(); use a "
48 "specialization [Implementation="
49 << implementation << "].";
50}
51
52template <Implementation implementation>
53std::vector<std::vector<uint32_t>> AllPairsShortestPaths(
54 const Graph& graph, const std::vector<uint32_t>& arc_costs,
55 int num_threads) {
56 std::vector<int> all_nodes(graph.num_nodes(), -1);
57 std::iota(all_nodes.begin(), all_nodes.end(), 0);
58 return ManyToManyShortestPaths<implementation>(graph, arc_costs, all_nodes,
59 all_nodes, num_threads);
60}
61
62template <>
63std::vector<std::vector<uint32_t>> ManyToManyShortestPaths<BOUNDED_DIJKSTRA>(
64 const Graph& graph, const std::vector<uint32_t>& arc_costs,
65 const std::vector<int>& srcs, const std::vector<int>& dsts,
66 int num_threads) {
67 using Dijkstra = BoundedDijkstraWrapper<Graph, uint32_t>;
68 Dijkstra base_dijkstra(&graph, &arc_costs);
69 std::vector<std::pair<int, uint32_t>> dsts_with_offsets;
70 for (const int dst : dsts) {
71 dsts_with_offsets.push_back({dst, 0});
72 }
73 ThreadLocal<Dijkstra> thread_local_dijkstra(base_dijkstra);
74 std::vector<std::vector<uint32_t>> distances(
75 srcs.size(), std::vector<uint32_t>(dsts.size(), INT_MAX));
76 std::vector<int> src_to_src_index(graph.num_nodes(), -1);
77 for (int i = 0; i < srcs.size(); ++i) {
78 src_to_src_index[srcs[i]] = i;
79 }
80 std::vector<int> dst_to_dst_index(graph.num_nodes(), -1);
81 for (int i = 0; i < dsts.size(); ++i) {
82 dst_to_dst_index[dsts[i]] = i;
83 }
84 // clang-format off
85 fiber_auto_design::Parallelizer(num_threads).Apply(
86 [&thread_local_dijkstra, &dsts_with_offsets, &src_to_src_index,
87 &dst_to_dst_index, &distances](const int* src_ptr) {
88 const int src = *src_ptr;
89 Dijkstra* const dij = thread_local_dijkstra.pointer();
90 for (const int destination :
91 dij->RunBoundedDijkstraFromMultipleSourcesToMultipleDestinations(
92 {{src, 0}}, dsts_with_offsets,
93 /*num_destinations_to_reach=*/dsts_with_offsets.size(),
94 /*distance_limit=*/INT_MAX)) {
95 distances[src_to_src_index[src]][dst_to_dst_index[destination]] =
96 dij->distances()[destination];
97 }
98 },
99 &srcs);
100 // clang-format on
101 return distances;
102}
103
104template <>
105std::vector<std::vector<uint32_t>> ManyToManyShortestPaths<SHORTEST_PATHS>(
106 const Graph& graph, const std::vector<uint32_t>& arc_costs,
107 const std::vector<int>& srcs, const std::vector<int>& dsts,
108 int num_threads) {
109 PathContainer path_container;
112 graph, arc_costs, srcs, dsts, num_threads, &path_container);
113 std::vector<std::vector<uint32_t>> distances(
114 srcs.size(), std::vector<uint32_t>(dsts.size(), INT_MAX));
115 std::vector<int> src_to_src_index(graph.num_nodes(), -1);
116 for (int i = 0; i < srcs.size(); ++i) {
117 src_to_src_index[srcs[i]] = i;
118 }
119 std::vector<int> dst_to_dst_index(graph.num_nodes(), -1);
120 for (int i = 0; i < dsts.size(); ++i) {
121 dst_to_dst_index[dsts[i]] = i;
122 }
123 for (const int src : srcs) {
124 for (const int dst : dsts) {
125 distances[src_to_src_index[src]][dst_to_dst_index[dst]] =
126 path_container.GetDistance(src, dst);
127 }
128 }
129 return distances;
130}
131
132template <Implementation implementation>
133static void BM_MultiThreadAllPairsOn2DGrid(benchmark::State& state) {
134 // Benchmark arguments: grid size and number of threads.
135 const int grid_size = state.range(0);
136 const int num_threads = state.range(1);
137
138 std::unique_ptr<Graph> graph =
139 util::Create2DGridGraph<Graph>(grid_size, grid_size);
140 std::vector<uint32_t> arc_costs(graph->num_arcs(), 0);
141 std::mt19937 random(12345);
142 for (uint32_t& cost : arc_costs) {
143 cost = absl::Uniform(random, 0, 100000);
144 }
145 for (auto _ : state) {
146 ::benchmark::DoNotOptimize(
147 AllPairsShortestPaths<implementation>(*graph, arc_costs, num_threads));
148 }
149 // "byte" = pair of nodes for which we computed the shortest path distance.
150 state.SetBytesProcessed(state.iterations() * graph->num_nodes() *
151 graph->num_nodes());
152}
153
154// NOTE(user): Sadly, RangePair() doesn't give us the range we want, and
155// there's no easy way to avoid duplicating the big list of ArgPair().
156BENCHMARK(BM_MultiThreadAllPairsOn2DGrid<BOUNDED_DIJKSTRA>)
157 ->ArgPair(/*grid_size*/ 8, /*num_threads*/ 1)
158 ->ArgPair(/*grid_size*/ 8, /*num_threads*/ 8)
159 ->ArgPair(/*grid_size*/ 8, /*num_threads*/ 16)
160 ->ArgPair(/*grid_size*/ 16, /*num_threads*/ 1)
161 ->ArgPair(/*grid_size*/ 16, /*num_threads*/ 8)
162 ->ArgPair(/*grid_size*/ 16, /*num_threads*/ 16)
163 ->ArgPair(/*grid_size*/ 64, /*num_threads*/ 1)
164 ->ArgPair(/*grid_size*/ 64, /*num_threads*/ 8)
165 ->ArgPair(/*grid_size*/ 64, /*num_threads*/ 16)
166 // For the larger size, just run with 8 threads: 1 thread is too slow.
167 ->ArgPair(/*grid_size*/ 128, /*num_threads*/ 8);
168BENCHMARK(BM_MultiThreadAllPairsOn2DGrid<SHORTEST_PATHS>)
169 ->ArgPair(/*grid_size*/ 8, /*num_threads*/ 1)
170 ->ArgPair(/*grid_size*/ 8, /*num_threads*/ 8)
171 ->ArgPair(/*grid_size*/ 8, /*num_threads*/ 16)
172 ->ArgPair(/*grid_size*/ 16, /*num_threads*/ 1)
173 ->ArgPair(/*grid_size*/ 16, /*num_threads*/ 8)
174 ->ArgPair(/*grid_size*/ 16, /*num_threads*/ 16)
175 ->ArgPair(/*grid_size*/ 64, /*num_threads*/ 1)
176 ->ArgPair(/*grid_size*/ 64, /*num_threads*/ 8)
177 ->ArgPair(/*grid_size*/ 64, /*num_threads*/ 16)
178 ->ArgPair(/*grid_size*/ 128, /*num_threads*/ 8);
179
180template <Implementation implementation, int num_threads>
181static void BM_WindowedAllPairsOn2DGrid(benchmark::State& state) {
182 // Benchmark arguments: total grid size and "window" size, i.e. the
183 // size of the sub-square within which we'll pick all of our sources and
184 // destinations (all nodes in the window).
185 const int grid_size = state.range(0);
186 const int window_size = state.range(1);
187
188 std::unique_ptr<Graph> graph =
189 util::Create2DGridGraph<Graph>(grid_size, grid_size);
190 std::vector<uint32_t> arc_costs(graph->num_arcs(), 0);
191 std::mt19937 random(12345);
192 for (uint32_t& cost : arc_costs) {
193 cost = absl::Uniform(random, 0, 100000);
194 }
195 std::vector<int> window_nodes;
196 const int window_start = (grid_size - window_size) / 2;
197 const int window_end = window_start + window_size;
198 for (int i = window_start; i < window_end; ++i) {
199 for (int j = window_start; j < window_end; ++j) {
200 window_nodes.push_back(i * grid_size + j);
201 }
202 }
203 for (auto _ : state) {
204 ::benchmark::DoNotOptimize(ManyToManyShortestPaths<implementation>(
205 *graph, arc_costs, window_nodes, window_nodes, num_threads));
206 }
207 // "byte" = pair of nodes for which we computed the shortest path distance.
208 state.SetBytesProcessed(state.iterations() * window_size * window_size);
209}
210
211BENCHMARK(BM_WindowedAllPairsOn2DGrid<BOUNDED_DIJKSTRA, 1>)
212 ->ArgPair(100, 10)
213 ->ArgPair(1000, 10)
214 ->ArgPair(500, 50);
215BENCHMARK(BM_WindowedAllPairsOn2DGrid<SHORTEST_PATHS, 1>)
216 ->ArgPair(100, 10)
217 ->ArgPair(1000, 10)
218 ->ArgPair(500, 50);
219BENCHMARK(BM_WindowedAllPairsOn2DGrid<BOUNDED_DIJKSTRA, 8>)
220 ->ArgPair(100, 10)
221 ->ArgPair(1000, 10)
222 ->ArgPair(500, 50);
223BENCHMARK(BM_WindowedAllPairsOn2DGrid<SHORTEST_PATHS, 8>)
224 ->ArgPair(100, 10)
225 ->ArgPair(1000, 10)
226 ->ArgPair(500, 50);
227BENCHMARK(BM_WindowedAllPairsOn2DGrid<BOUNDED_DIJKSTRA, 16>)
228 ->ArgPair(100, 10)
229 ->ArgPair(1000, 10)
230 ->ArgPair(500, 50);
231BENCHMARK(BM_WindowedAllPairsOn2DGrid<SHORTEST_PATHS, 16>)
232 ->ArgPair(100, 10)
233 ->ArgPair(1000, 10)
234 ->ArgPair(500, 50);
235
236} // namespace
237} // namespace operations_research
static void BuildPathDistanceContainer(PathContainer *path_container)
Builds a path container which only stores distances between path nodes.
GraphType graph
In SWIG mode, we don't want anything besides these top-level includes.
util::ReverseArcStaticGraph Graph
Type of graph to use.
void ComputeManyToManyShortestPathsWithMultipleThreads(const ListGraph<> &graph, const std::vector< PathDistance > &arc_lengths, const std::vector< ListGraph<>::NodeIndex > &sources, const std::vector< ListGraph<>::NodeIndex > &destinations, int num_threads, PathContainer *const path_container)