osrm-backend/src/partitioner/recursive_bisection.cpp
2024-05-06 09:14:46 +02:00

117 lines
4.1 KiB
C++

#include "partitioner/recursive_bisection.hpp"
#include "partitioner/inertial_flow.hpp"
#include "partitioner/bisection_graph_view.hpp"
#include "partitioner/recursive_bisection_state.hpp"
#include "util/log.hpp"
#include "util/timing_util.hpp"
#include <tbb/parallel_for_each.h>
#include <algorithm>
#include <climits> // for CHAR_BIT
#include <cstddef>
#include <iterator>
#include <unordered_map>
#include <utility>
#include <vector>
namespace osrm::partitioner
{
RecursiveBisection::RecursiveBisection(BisectionGraph &bisection_graph_,
const std::size_t maximum_cell_size,
const double balance,
const double boundary_factor,
const std::size_t num_optimizing_cuts,
const std::size_t small_component_size)
: bisection_graph(bisection_graph_), internal_state(bisection_graph_)
{
auto components = internal_state.PrePartitionWithSCC(small_component_size);
BOOST_ASSERT(!components.empty());
// Parallelize recursive bisection trees. Root cut happens serially (well, this is a lie:
// since we handle big components in parallel, too. But we don't know this and
// don't have to. TBB's scheduler handles nested parallelism just fine).
//
// [ | ]
// / \ root cut
// [ | ] [ | ]
// / \ / \ descend, do cuts in parallel
//
// https://www.threadingbuildingblocks.org/docs/help/index.htm#reference/algorithms/parallel_do_func.html
struct TreeNode
{
BisectionGraphView graph;
std::uint64_t depth;
};
// Build a recursive bisection tree for all big components independently in parallel.
// Last GraphView is all small components: skip for bisection.
auto first = begin(components);
auto last = end(components) - 1;
// We construct the trees on the fly: the root node is the entry point.
// All tree branches depend on the actual cut and will be generated while descending.
std::vector<TreeNode> forest;
forest.reserve(last - first);
std::transform(first,
last,
std::back_inserter(forest),
[this](auto graph) {
return TreeNode{std::move(graph), internal_state.SCCDepth()};
});
using Feeder = tbb::feeder<TreeNode>;
TIMER_START(bisection);
// Bisect graph into two parts. Get partition point and recurse left and right in parallel.
tbb::parallel_for_each(
begin(forest),
end(forest),
[&](const TreeNode &node, Feeder &feeder)
{
const auto cut =
computeInertialFlowCut(node.graph, num_optimizing_cuts, balance, boundary_factor);
const auto center = internal_state.ApplyBisection(
node.graph.Begin(), node.graph.End(), node.depth, cut.flags);
const auto terminal = [&](const auto &node)
{
const auto maximum_depth = sizeof(BisectionID) * CHAR_BIT;
const auto too_small = node.graph.NumberOfNodes() < maximum_cell_size;
const auto too_deep = node.depth >= maximum_depth;
return too_small || too_deep;
};
BisectionGraphView left_graph{bisection_graph, node.graph.Begin(), center};
TreeNode left_node{std::move(left_graph), node.depth + 1};
if (!terminal(left_node))
feeder.add(left_node);
BisectionGraphView right_graph{bisection_graph, center, node.graph.End()};
TreeNode right_node{std::move(right_graph), node.depth + 1};
if (!terminal(right_node))
feeder.add(right_node);
});
TIMER_STOP(bisection);
util::Log() << "Full bisection done in " << TIMER_SEC(bisection) << "s";
}
const std::vector<BisectionID> &RecursiveBisection::BisectionIDs() const
{
return internal_state.BisectionIDs();
}
std::uint32_t RecursiveBisection::SCCDepth() const { return internal_state.SCCDepth(); }
} // namespace osrm::partitioner