diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 523688f40..19ed60dab 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -99,6 +99,7 @@ jobs: -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DFICTION_CLI=ON -DFICTION_TEST=ON + -DFICTION_BENCHMARK=ON -DFICTION_EXPERIMENTS=ON -DFICTION_Z3=ON -DFICTION_PROGRESS_BARS=OFF diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 98c2a401f..9f462e99c 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -107,6 +107,7 @@ jobs: -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DFICTION_CLI=ON -DFICTION_TEST=ON + -DFICTION_BENCHMARK=ON -DFICTION_EXPERIMENTS=ON -DFICTION_Z3=ON -DFICTION_ENABLE_MUGEN=ON diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 3b457ace2..00054c4fd 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -83,6 +83,7 @@ jobs: -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DFICTION_CLI=ON -DFICTION_TEST=ON + -DFICTION_BENCHMARK=ON -DFICTION_EXPERIMENTS=ON -DFICTION_Z3=ON -DMOCKTURTLE_EXAMPLES=OFF diff --git a/docs/algorithms/path_finding.rst b/docs/algorithms/path_finding.rst index f4e493d9f..fcc67599c 100644 --- a/docs/algorithms/path_finding.rst +++ b/docs/algorithms/path_finding.rst @@ -7,11 +7,35 @@ Distance functions compute (an approximation for) the distance between two coord .. doxygenfunction:: fiction::manhattan_distance .. doxygenfunction:: fiction::euclidean_distance +.. doxygenfunction:: fiction::twoddwave_distance .. doxygenclass:: fiction::distance_functor :members: .. doxygenclass:: fiction::manhattan_distance_functor .. doxygenclass:: fiction::euclidean_distance_functor +.. doxygenclass:: fiction::twoddwave_distance_functor + +Distance Maps +------------- + +Distance maps can store the distance from a coordinate to all other coordinates. They are particularly useful when +repeatedly calling complex distance functions that are expensive to evaluate. The distance maps can serve as a +lookup-table for these cases. + +**Header:** ``fiction/algorithms/path_finding/distance_map.hpp`` + +.. doxygentypedef:: fiction::distance_map +.. doxygentypedef:: fiction::sparse_distance_map + +.. doxygenfunction:: fiction::initialize_distance_map +.. doxygenfunction:: fiction::initialize_sparse_distance_map + +.. doxygenclass:: fiction::distance_map_functor + :members: +.. doxygenclass:: fiction::sparse_distance_map_functor + :members: +.. doxygenclass:: fiction::smart_distance_cache_functor + :members: Cost Functions -------------- diff --git a/docs/getting_started.rst b/docs/getting_started.rst index c6685661a..7a47e0848 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -153,6 +153,18 @@ linked against *fiction* and compiled as a stand-alone binary using the followin cmake --build . -j4 +Building code benchmarks +------------------------ + +Using ``Catch2``'s micro-benchmarking feature, you can compile and run code tests that evaluate the performance of +certain code constructs. The ``test/benchmark`` folder provides a selection of benchmarks we were running to evaluate +the performance of our code during development. Any ``*.cpp`` file that is placed in that folder is automatically +linked against *fiction* and compiled as a stand-alone binary using the following commands:: + + cmake . -B build -DFICTION_BENCHMARK=ON + cd build + cmake --build . -j4 + Uninstall --------- diff --git a/include/fiction/algorithms/path_finding/distance.hpp b/include/fiction/algorithms/path_finding/distance.hpp index 7eeddbfa8..9c4f2ff71 100644 --- a/include/fiction/algorithms/path_finding/distance.hpp +++ b/include/fiction/algorithms/path_finding/distance.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace fiction @@ -62,6 +63,33 @@ template return static_cast(std::hypot(x, y)); } +/** + * The 2DDWave distance \f$ D \f$ between two layout coordinates \f$ s = (x_1, y_1) \f$ and \f$ t = (x_2, y_2) \f$ given + * by + * + * \f$ D = |x_1 - x_2| + |y_1 - y_2| \f$ iff \f$ s \leq t \f$ and \f$ \infty \f$, otherwise. + * + * Thereby, \f$ s \leq t \f$ iff \f$ x_1 \leq x_2 \f$ and \f$ y_1 \leq y_2 \f$. + * + * @note To represent \f$ \infty \f$, `std::numeric_limits::max()` is returned for distances of infinite length. + * + * @tparam Lyt Coordinate layout type. + * @tparam Dist Integral type for the distance. + * @param lyt Layout. + * @param source Source coordinate. + * @param target Target coordinate. + * @return 2DDWave distance between `source` and `target`. + */ +template +[[nodiscard]] constexpr Dist twoddwave_distance([[maybe_unused]] const Lyt& lyt, const coordinate& source, + const coordinate& target) noexcept +{ + static_assert(is_coordinate_layout_v, "Lyt is not a coordinate layout"); + static_assert(std::is_integral_v, "Dist is not an integral type"); + + return source.x <= target.x && source.y <= target.y ? manhattan_distance(lyt, source, target) : + std::numeric_limits::max(); +} /** * Computes the distance between two SiDB cells in nanometers. * @@ -109,7 +137,7 @@ class distance_functor * @param dist_fn A function that maps from layout coordinates to a distance value. */ explicit distance_functor( - const std::function&, const coordinate&)>& dist_fn) : + const std::function&, const coordinate&)>& dist_fn) : distance_function{dist_fn} { static_assert(is_coordinate_layout_v, "Lyt is not a coordinate layout"); @@ -136,7 +164,7 @@ class distance_functor /** * Distance function. */ - const std::function&, const coordinate&)> distance_function; + const std::function&, const coordinate&)> distance_function; }; // NOLINTEND(*-special-member-functions) @@ -165,6 +193,18 @@ class euclidean_distance_functor : public distance_functor public: euclidean_distance_functor() : distance_functor(&euclidean_distance) {} }; +/** + * A pre-defined distance functor that uses the 2DDWave distance. + * + * @tparam Lyt Coordinate layout type. + * @tparam Dist Integral distance type. + */ +template +class twoddwave_distance_functor : public distance_functor +{ + public: + twoddwave_distance_functor() : distance_functor(&twoddwave_distance) {} +}; } // namespace fiction diff --git a/include/fiction/algorithms/path_finding/distance_map.hpp b/include/fiction/algorithms/path_finding/distance_map.hpp new file mode 100644 index 000000000..d2aac4ea1 --- /dev/null +++ b/include/fiction/algorithms/path_finding/distance_map.hpp @@ -0,0 +1,274 @@ +// +// Created by marcel on 18.06.23. +// + +#ifndef FICTION_DISTANCE_MAP_HPP +#define FICTION_DISTANCE_MAP_HPP + +#include "fiction/algorithms/path_finding/distance.hpp" +#include "fiction/traits.hpp" + +#include + +#include +#include +#include +#include + +namespace fiction +{ + +/** + * A distance map is a two-dimensional array of distances between coordinates. The distance map is accessed via the + * indices of the coordinates in the associated layout. A coordinate index \f$ i \f$ is calculated as follows: + * \f$ i = y \cdot W + x \f$, with \f$ W \f$ being the width of the layout. As an example, in a layout of size + * \f$ 3 \times 3 \f$, the coordinate index of the coordinate \f$ (2, 1) \f$ has index \f$ 1 \cdot 3 + 2 = 5 \f$. + * + * The `distance_map` is to be preferred over the `sparse_distance_map` in most cases when performance is the main goal. + */ +template +using distance_map = std::vector>; +/** + * A sparse distance map is a flat hash map of distances between coordinates. The sparse distance map is accessed via + * coordinate pairs. The sparse distance map is to be preferred over the distance map if only a small subset of the + * distances between coordinates is to be stored. + * + * The `distance_map` is to be preferred over the `sparse_distance_map` in most cases when performance is the main goal. + */ +template +using sparse_distance_map = phmap::parallel_flat_hash_map, coordinate>, Dist>; +/** + * This function fully initializes a `distance_map` for a given layout and distance functor. It computes the distances + * between all pairs of coordinates in the layout and stores them in the distance map for quick subsequent access. + * + * This function performs \f$ \mathcal{O}(|L|^2) \f$ distance computations, where \f$ |L| \f$ is the number of + * coordinates in the layout. + * + * @tparam Lyt Coordinate layout type. + * @tparam Dist Distance type. + * @param lyt Layout to compute distances for. + * @param dist_fn Distance functor to apply to all pairs of coordinates in `lyt`. + * @return Fully initialized `distance_map` for `lyt`. + */ +template +[[nodiscard]] distance_map initialize_distance_map(const Lyt& lyt, + const distance_functor& dist_fn) noexcept +{ + static_assert(is_coordinate_layout_v, "Lyt is not a coordinate layout"); + + // initialize distance map + distance_map dist_map(lyt.area(), std::vector(lyt.area(), Dist{})); + + // compute distances and store them via coordinate index + lyt.foreach_coordinate( + [&lyt, &dist_fn, &dist_map](const auto& c1, const unsigned src) + { + lyt.foreach_coordinate([&lyt, &dist_fn, &dist_map, &c1, src](const auto& c2, const unsigned tgt) + { dist_map[src][tgt] = dist_fn(lyt, c1, c2); }); + }); + + return dist_map; +} +/** + * This function fully initializes a `sparse_distance_map` for a given layout and distance functor. It computes the + * distances between all pairs of coordinates in the layout and stores them in the distance map for quick subsequent + * access. + * + * This function performs \f$ \mathcal{O}(|L|^2) \f$ distance computations, where \f$ |L| \f$ is the number of + * coordinates in the layout. + * + * @tparam Lyt Coordinate layout type. + * @tparam Dist Distance type. + * @param lyt Layout to compute distances for. + * @param dist_fn Distance functor to apply to all pairs of coordinates in `lyt`. + * @return Fully initialized `sparse_distance_map` for `lyt`. + */ +template +[[nodiscard]] sparse_distance_map +initialize_sparse_distance_map(const Lyt& lyt, const distance_functor& dist_fn) noexcept +{ + static_assert(is_coordinate_layout_v, "Lyt is not a coordinate layout"); + + // initialize distance map + sparse_distance_map dist_map{}; + dist_map.reserve(lyt.area() * lyt.area()); + + // compute distances and store them via coordinate lookup + lyt.foreach_coordinate( + [&lyt, &dist_fn, &dist_map](const auto& c1) + { + lyt.foreach_coordinate( + [&lyt, &dist_fn, &dist_map, &c1](const auto& c2) { + dist_map[{c1, c2}] = dist_fn(lyt, c1, c2); + }); + }); + + return dist_map; +} + +/** + * A distance functor that uses a fully precomputed `distance_map` to determine distances between coordinates. It can be + * used as a drop-in replacement for any other distance functor in path-finding algorithms. + * + * @tparam Lyt Coordinate layout type. + * @tparam Dist Distance type. + */ +template +class distance_map_functor : public distance_functor +{ + public: + /** + * Construct the distance functor from a `distance_map`. + * + * @param dm Distance map. + */ + explicit distance_map_functor(const distance_map& dm) : + distance_functor([](const Lyt&, const coordinate&, const coordinate&) + { return Dist{}; }), // dummy distance function + dist_map{dm} + {} + /** + * Override the call operator to query the distance map instead of the distance function. + * + * @note This function will throw an exception if the queried distance is not stored in the distance map. + * + * @param lyt Layout. + * @param source Source coordinate. + * @param target Target coordinate. + * @return Distance between source and target according to the stored distance map. + */ + [[nodiscard]] Dist operator()(const Lyt& lyt, const coordinate& source, + const coordinate& target) const override + { + return static_cast(dist_map.at(coordinate_index(lyt, source)).at(coordinate_index(lyt, target))); + } + + protected: + /** + * Distance map. + */ + const distance_map dist_map; + + private: + /** + * This function calculates the coordinate index of a given coordinate in a given layout. + * + * @param lyt Layout. + * @param c Coordinate. + * @return Coordinate index. + */ + [[nodiscard]] static constexpr std::size_t coordinate_index(const Lyt& lyt, const coordinate& c) noexcept + { + return c.y * (lyt.x() + 1) + c.x; + } +}; +/** + * A distance functor that uses a fully precomputed `sparse_distance_map` to determine distances between coordinates. It + * can be used as a drop-in replacement for any other distance functor in path-finding algorithms. + * + * @tparam Lyt Coordinate layout type. + * @tparam Dist Distance type. + */ +template +class sparse_distance_map_functor : public distance_functor +{ + public: + /** + * Construct the distance functor from a sparse distance map. + * + * @param sdm Sparse distance map. + */ + explicit sparse_distance_map_functor(const sparse_distance_map& sdm) : + distance_functor([](const Lyt&, const coordinate&, const coordinate&) + { return Dist{}; }), // dummy distance function + sparse_dist_map{sdm} + {} + /** + * Override the call operator to query the sparse distance map instead of the distance function. + * + * @note This function will throw an exception if the queried distance is not stored in the sparse distance map. + * + * @param lyt Layout. + * @param source Source coordinate. + * @param target Target coordinate. + * @return Distance between source and target according to the stored sparse distance map. + */ + [[nodiscard]] Dist operator()(const Lyt&, const coordinate& source, + const coordinate& target) const override + { + return static_cast(sparse_dist_map.at({source, target})); + } + + protected: + /** + * Sparse distance map. + */ + const sparse_distance_map sparse_dist_map; +}; + +/** + * A distance functor that internally uses a `sparse_distance_map` as a cache to prevent re-computing distances that + * have already been evaluated. In contrast to `distance_map_functor` and `sparse_distance_map_functor`, this functor + * does not require a pre-computed distance map upon construction, but instead will gradually build up its own cache + * when queried multiple times. It can be used as a drop-in replacement for any other distance functor in path-finding + * algorithms. + * + * @tparam Lyt Coordinate layout type. + * @tparam Dist Distance type. + */ +template +class smart_distance_cache_functor : public distance_functor +{ + public: + /** + * Construct a distance functor from a layout and a distance function. + * + * The internal cache will be initialized empty. Distances will be computed on the fly and stored in the cache + * whenever they are queried. + * + * @param lyt Layout. + * @param dist_fn Distance function. + */ + explicit smart_distance_cache_functor( + const Lyt& lyt, + const std::function&, const coordinate&)>& dist_fn) : + distance_functor(dist_fn) + { + distance_cache.reserve(lyt.area() * lyt.area()); + } + /** + * Override the call operator to first query the cache instead of the distance function. Only on a cache miss, + * the distance function will be called and the result will be stored in the cache. + * + * @param lyt Layout. + * @param source Source coordinate. + * @param target Target coordinate. + * @return Distance between source and target according to the cache or the distance function. + */ + [[nodiscard]] Dist operator()(const Lyt& lyt, const coordinate& source, + const coordinate& target) const override + { + if (const auto it = distance_cache.find({source, target}); it != distance_cache.cend()) // cache hit + { + return it->second; + } + + // cache miss + + const auto d = distance_functor::operator()(lyt, source, target); + + distance_cache[{source, target}] = d; + + return d; + } + + protected: + /** + * Sparse distance map serving as a cache. + */ + mutable sparse_distance_map distance_cache{}; +}; + +} // namespace fiction + +#endif // FICTION_DISTANCE_MAP_HPP diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 40db17758..4a17de344 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,14 @@ +# Benchmarking (depends on Catch2) +option(FICTION_BENCHMARK "Build fiction benchmarks, which can evaluate the performance of certain code fragments" OFF) +if (FICTION_BENCHMARK) + message(STATUS "Building fiction benchmarks") + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/benchmark) +endif () + include_directories(.) file(GLOB_RECURSE FILENAMES */*.cpp) +list(FILTER FILENAMES EXCLUDE REGEX "benchmark/.*$") foreach (FILE IN LISTS FILENAMES) get_filename_component(NAME ${FILE} NAME_WE) diff --git a/test/algorithms/path_finding/distance.cpp b/test/algorithms/path_finding/distance.cpp index eb6a902e0..8568e3099 100644 --- a/test/algorithms/path_finding/distance.cpp +++ b/test/algorithms/path_finding/distance.cpp @@ -213,6 +213,106 @@ TEST_CASE("Euclidean distance functor", "[distance]") } } +TEST_CASE("2DDWave distance", "[distance]") +{ + SECTION("Unsigned Cartesian layout") + { + using cart_lyt = cartesian_layout; + + const cart_lyt layout{}; + + CHECK(twoddwave_distance(layout, {0, 0}, {0, 0}) == 0); + CHECK(twoddwave_distance(layout, {1, 1}, {1, 1}) == 0); + CHECK(twoddwave_distance(layout, {0, 0}, {0, 1}) == 1); + CHECK(twoddwave_distance(layout, {0, 0}, {1, 1}) == 2); + CHECK(twoddwave_distance(layout, {1, 2}, {3, 3}) == 3); + CHECK(twoddwave_distance(layout, {0, 0}, {4, 4}) == 8); + CHECK(twoddwave_distance(layout, {4, 4}, {0, 0}) == std::numeric_limits::max()); + CHECK(twoddwave_distance(layout, {2, 1}, {0, 2}) == std::numeric_limits::max()); + CHECK(twoddwave_distance(layout, {1, 0}, {0, 1}) == std::numeric_limits::max()); + + // ignore z-axis + CHECK(twoddwave_distance(layout, {0, 0, 1}, {8, 9, 0}) == 17); + CHECK(twoddwave_distance(layout, {0, 0, 1}, {8, 9, 1}) == 17); + } + SECTION("Signed Cartesian layout") + { + using cart_lyt = cartesian_layout; + + const cart_lyt layout{}; + + CHECK(twoddwave_distance(layout, {0, 0}, {0, 0}) == 0); + CHECK(twoddwave_distance(layout, {1, 1}, {1, 1}) == 0); + CHECK(twoddwave_distance(layout, {0, 0}, {0, 1}) == 1); + CHECK(twoddwave_distance(layout, {0, 0}, {1, 1}) == 2); + CHECK(twoddwave_distance(layout, {1, 2}, {3, 3}) == 3); + CHECK(twoddwave_distance(layout, {0, 0}, {4, 4}) == 8); + CHECK(twoddwave_distance(layout, {4, 4}, {0, 0}) == std::numeric_limits::max()); + CHECK(twoddwave_distance(layout, {2, 1}, {0, 2}) == std::numeric_limits::max()); + CHECK(twoddwave_distance(layout, {1, 0}, {0, 1}) == std::numeric_limits::max()); + + // ignore z-axis + CHECK(twoddwave_distance(layout, {0, 0, 1}, {8, 9, 0}) == 17); + CHECK(twoddwave_distance(layout, {0, 0, 1}, {8, 9, 1}) == 17); + + // negative coordinates + CHECK(twoddwave_distance(layout, {0, 0}, {-1, -1}) == std::numeric_limits::max()); + CHECK(twoddwave_distance(layout, {-2, -8}, {-6, -4}) == std::numeric_limits::max()); + CHECK(twoddwave_distance(layout, {-6, -4}, {-2, -8}) == std::numeric_limits::max()); + CHECK(twoddwave_distance(layout, {-4, -3}, {1, -1}) == 7); + } +} + +TEST_CASE("2DDWave distance functor", "[distance]") +{ + SECTION("Unsigned Cartesian layout") + { + using cart_lyt = cartesian_layout; + + const cart_lyt layout{}; + + const twoddwave_distance_functor distance{}; + + CHECK(distance(layout, {0, 0}, {0, 0}) == 0); + CHECK(distance(layout, {1, 1}, {1, 1}) == 0); + CHECK(distance(layout, {0, 0}, {0, 1}) == 1); + CHECK(distance(layout, {0, 0}, {1, 1}) == 2); + CHECK(distance(layout, {1, 2}, {3, 3}) == 3); + CHECK(distance(layout, {0, 0}, {4, 4}) == 8); + CHECK(distance(layout, {4, 4}, {0, 0}) == std::numeric_limits::max()); + + // ignore z-axis + CHECK(distance(layout, {0, 0, 1}, {8, 9, 0}) == 17); + CHECK(distance(layout, {0, 0, 1}, {8, 9, 1}) == 17); + } + SECTION("Signed Cartesian layout") + { + using cart_lyt = cartesian_layout; + + const cart_lyt layout{}; + + const twoddwave_distance_functor distance{}; + + CHECK(distance(layout, {0, 0}, {0, 0}) == 0); + CHECK(distance(layout, {1, 1}, {1, 1}) == 0); + CHECK(distance(layout, {0, 0}, {0, 1}) == 1); + CHECK(distance(layout, {0, 0}, {1, 1}) == 2); + CHECK(distance(layout, {1, 2}, {3, 3}) == 3); + CHECK(distance(layout, {0, 0}, {4, 4}) == 8); + CHECK(distance(layout, {4, 4}, {0, 0}) == std::numeric_limits::max()); + + // ignore z-axis + CHECK(distance(layout, {0, 0, 1}, {8, 9, 0}) == 17); + CHECK(distance(layout, {0, 0, 1}, {8, 9, 1}) == 17); + + // negative coordinates + CHECK(distance(layout, {0, 0}, {-1, -1}) == std::numeric_limits::max()); + CHECK(distance(layout, {-2, -8}, {-6, -4}) == std::numeric_limits::max()); + CHECK(distance(layout, {-6, -4}, {-2, -8}) == std::numeric_limits::max()); + CHECK(distance(layout, {-4, -3}, {1, -1}) == 7); + } +} + TEST_CASE("A* distance", "[distance]") { SECTION("Unsigned Cartesian layout") diff --git a/test/algorithms/path_finding/distance_map.cpp b/test/algorithms/path_finding/distance_map.cpp new file mode 100644 index 000000000..8be5a483e --- /dev/null +++ b/test/algorithms/path_finding/distance_map.cpp @@ -0,0 +1,266 @@ +// +// Created by marcel on 18.06.23. +// + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace fiction; + +TEST_CASE("Distance map", "[distance-map]") +{ + using clk_lyt = clocked_layout>; + using dist = uint64_t; + + SECTION("2DDWave clocking") + { + const clk_lyt layout{aspect_ratio{4, 4}, twoddwave_clocking()}; + + const auto dist_map = initialize_distance_map(layout, a_star_distance_functor{}); + const auto dist_map_func = distance_map_functor{dist_map}; + + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func](const auto& c1, const unsigned src) + { + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func, &c1, src](const auto& c2, const unsigned tgt) + { + CHECK(dist_map[src][tgt] == twoddwave_distance(layout, c1, c2)); + CHECK(dist_map[src][tgt] == dist_map_func(layout, c1, c2)); + }); + }); + } + SECTION("USE clocking") + { + const clk_lyt layout{aspect_ratio{4, 4}, use_clocking()}; + + const auto dist_map = initialize_distance_map(layout, a_star_distance_functor{}); + const auto dist_map_func = distance_map_functor{dist_map}; + + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func](const auto& c1, const unsigned src) + { + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func, &c1, src](const auto& c2, const unsigned tgt) + { + CHECK(dist_map[src][tgt] == a_star_distance(layout, c1, c2)); + CHECK(dist_map[src][tgt] == dist_map_func(layout, c1, c2)); + }); + }); + } + SECTION("RES clocking") + { + const clk_lyt layout{aspect_ratio{4, 4}, res_clocking()}; + + const auto dist_map = initialize_distance_map(layout, a_star_distance_functor{}); + const auto dist_map_func = distance_map_functor{dist_map}; + + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func](const auto& c1, const unsigned src) + { + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func, &c1, src](const auto& c2, const unsigned tgt) + { + CHECK(dist_map[src][tgt] == a_star_distance(layout, c1, c2)); + CHECK(dist_map[src][tgt] == dist_map_func(layout, c1, c2)); + }); + }); + } + SECTION("CFE clocking") + { + const clk_lyt layout{aspect_ratio{4, 4}, cfe_clocking()}; + + const auto dist_map = initialize_distance_map(layout, a_star_distance_functor{}); + const auto dist_map_func = distance_map_functor{dist_map}; + + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func](const auto& c1, const unsigned src) + { + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func, &c1, src](const auto& c2, const unsigned tgt) + { + CHECK(dist_map[src][tgt] == a_star_distance(layout, c1, c2)); + CHECK(dist_map[src][tgt] == dist_map_func(layout, c1, c2)); + }); + }); + } +} + +TEST_CASE("Sparse distance map", "[distance-map]") +{ + using clk_lyt = clocked_layout>; + using dist = uint64_t; + + SECTION("2DDWave clocking") + { + const clk_lyt layout{aspect_ratio{4, 4}, twoddwave_clocking()}; + + const auto dist_map = initialize_sparse_distance_map(layout, a_star_distance_functor{}); + const auto dist_map_func = sparse_distance_map_functor{dist_map}; + + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func](const auto& c1) + { + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func, &c1](const auto& c2) + { + CHECK(dist_map.at({c1, c2}) == twoddwave_distance(layout, c1, c2)); + CHECK(dist_map.at({c1, c2}) == dist_map_func(layout, c1, c2)); + }); + }); + } + SECTION("USE clocking") + { + const clk_lyt layout{aspect_ratio{4, 4}, use_clocking()}; + + const auto dist_map = initialize_sparse_distance_map(layout, a_star_distance_functor{}); + const auto dist_map_func = sparse_distance_map_functor{dist_map}; + + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func](const auto& c1) + { + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func, &c1](const auto& c2) + { + CHECK(dist_map.at({c1, c2}) == a_star_distance(layout, c1, c2)); + CHECK(dist_map.at({c1, c2}) == dist_map_func(layout, c1, c2)); + }); + }); + } + SECTION("RES clocking") + { + const clk_lyt layout{aspect_ratio{4, 4}, res_clocking()}; + + const auto dist_map = initialize_sparse_distance_map(layout, a_star_distance_functor{}); + const auto dist_map_func = sparse_distance_map_functor{dist_map}; + + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func](const auto& c1) + { + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func, &c1](const auto& c2) + { + CHECK(dist_map.at({c1, c2}) == a_star_distance(layout, c1, c2)); + CHECK(dist_map.at({c1, c2}) == dist_map_func(layout, c1, c2)); + }); + }); + } + SECTION("CFE clocking") + { + const clk_lyt layout{aspect_ratio{4, 4}, cfe_clocking()}; + + const auto dist_map = initialize_sparse_distance_map(layout, a_star_distance_functor{}); + const auto dist_map_func = sparse_distance_map_functor{dist_map}; + + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func](const auto& c1) + { + layout.foreach_coordinate( + [&layout, &dist_map, &dist_map_func, &c1](const auto& c2) + { + CHECK(dist_map.at({c1, c2}) == a_star_distance(layout, c1, c2)); + CHECK(dist_map.at({c1, c2}) == dist_map_func(layout, c1, c2)); + }); + }); + } +} + +TEST_CASE("Smart distance cache functor", "[distance-map]") +{ + using clk_lyt = clocked_layout>; + using dist = uint64_t; + + SECTION("2DDWave clocking") + { + const clk_lyt layout{aspect_ratio{4, 4}, twoddwave_clocking()}; + + const auto dist_map_func = smart_distance_cache_functor{layout, &a_star_distance}; + + layout.foreach_coordinate( + [&layout, &dist_map_func](const auto& c1) + { + layout.foreach_coordinate( + [&layout, &dist_map_func, &c1](const auto& c2) + { CHECK(dist_map_func(layout, c1, c2) == twoddwave_distance(layout, c1, c2)); }); + }); + + // check that the cached distances are correct + layout.foreach_coordinate( + [&layout, &dist_map_func](const auto& c1) + { + layout.foreach_coordinate( + [&layout, &dist_map_func, &c1](const auto& c2) + { CHECK(dist_map_func(layout, c1, c2) == twoddwave_distance(layout, c1, c2)); }); + }); + } + SECTION("USE clocking") + { + const clk_lyt layout{aspect_ratio{4, 4}, use_clocking()}; + + const auto dist_map_func = smart_distance_cache_functor{layout, &a_star_distance}; + + layout.foreach_coordinate( + [&layout, &dist_map_func](const auto& c1) + { + layout.foreach_coordinate([&layout, &dist_map_func, &c1](const auto& c2) + { CHECK(dist_map_func(layout, c1, c2) == a_star_distance(layout, c1, c2)); }); + }); + + // check that the cached distances are correct + layout.foreach_coordinate( + [&layout, &dist_map_func](const auto& c1) + { + layout.foreach_coordinate([&layout, &dist_map_func, &c1](const auto& c2) + { CHECK(dist_map_func(layout, c1, c2) == a_star_distance(layout, c1, c2)); }); + }); + } + SECTION("RES clocking") + { + const clk_lyt layout{aspect_ratio{4, 4}, res_clocking()}; + + const auto dist_map_func = smart_distance_cache_functor{layout, &a_star_distance}; + + layout.foreach_coordinate( + [&layout, &dist_map_func](const auto& c1) + { + layout.foreach_coordinate([&layout, &dist_map_func, &c1](const auto& c2) + { CHECK(dist_map_func(layout, c1, c2) == a_star_distance(layout, c1, c2)); }); + }); + + // check that the cached distances are correct + layout.foreach_coordinate( + [&layout, &dist_map_func](const auto& c1) + { + layout.foreach_coordinate([&layout, &dist_map_func, &c1](const auto& c2) + { CHECK(dist_map_func(layout, c1, c2) == a_star_distance(layout, c1, c2)); }); + }); + } + SECTION("CFE clocking") + { + const clk_lyt layout{aspect_ratio{4, 4}, cfe_clocking()}; + + const auto dist_map_func = smart_distance_cache_functor{layout, &a_star_distance}; + + layout.foreach_coordinate( + [&layout, &dist_map_func](const auto& c1) + { + layout.foreach_coordinate([&layout, &dist_map_func, &c1](const auto& c2) + { CHECK(dist_map_func(layout, c1, c2) == a_star_distance(layout, c1, c2)); }); + }); + + // check that the cached distances are correct + layout.foreach_coordinate( + [&layout, &dist_map_func](const auto& c1) + { + layout.foreach_coordinate([&layout, &dist_map_func, &c1](const auto& c2) + { CHECK(dist_map_func(layout, c1, c2) == a_star_distance(layout, c1, c2)); }); + }); + } +} diff --git a/test/benchmark/CMakeLists.txt b/test/benchmark/CMakeLists.txt new file mode 100644 index 000000000..36c9d4864 --- /dev/null +++ b/test/benchmark/CMakeLists.txt @@ -0,0 +1,12 @@ +file(GLOB_RECURSE FILENAMES *.cpp) + +foreach (FILE IN LISTS FILENAMES) + get_filename_component(NAME ${FILE} NAME_WE) + set(BENCH_NAME bench_${NAME}) + add_executable(${BENCH_NAME} ${FILE}) + target_compile_definitions(${BENCH_NAME} INTERFACE CATCH_CONFIG_NO_POSIX_SIGNALS) # make catch2 ignore SIGTERMs sent to applications when timeouts are reached + target_link_libraries(${BENCH_NAME} PRIVATE fiction_project_warnings fiction_project_options libfiction Catch2::Catch2WithMain) + + add_test(NAME ${NAME} COMMAND ${BENCH_NAME}) # group tests by file + # catch_discover_tests(${BENCH_NAME}) +endforeach () diff --git a/test/benchmark/distance_map.cpp b/test/benchmark/distance_map.cpp new file mode 100644 index 000000000..ff436e876 --- /dev/null +++ b/test/benchmark/distance_map.cpp @@ -0,0 +1,82 @@ +// +// Created by marcel on 12.07.23. +// + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace fiction; + +template +Dist sum_distances(const Lyt& layout, const distance_functor& dist_func) noexcept +{ + Dist sum = 0; + layout.foreach_coordinate( + [&layout, &dist_func, &sum](const auto& c1) { + layout.foreach_coordinate([&layout, &dist_func, &sum, &c1](const auto& c2) + { sum += dist_func(layout, c1, c2); }); + }); + + return sum; +} + +TEST_CASE("Benchmark distance maps", "[benchmark]") +{ + using clk_lyt = clocked_layout>; + using dist = uint64_t; + + const clk_lyt layout{aspect_ratio{5, 5}, use_clocking()}; + + BENCHMARK("without distance maps") + { + return sum_distances(layout, a_star_distance_functor{}); + }; + + const auto dist_map = initialize_distance_map(layout, a_star_distance_functor{}); + const auto dist_map_func = distance_map_functor{dist_map}; + + BENCHMARK("distance_map") + { + return sum_distances(layout, dist_map_func); + }; + + const auto sparse_dist_map = initialize_sparse_distance_map(layout, a_star_distance_functor{}); + const auto sparse_dist_map_func = sparse_distance_map_functor{sparse_dist_map}; + + BENCHMARK("sparse_distance_map") + { + return sum_distances(layout, sparse_dist_map_func); + }; +} + +TEST_CASE("Benchmark smart distance cache", "[benchmark]") +{ + using clk_lyt = clocked_layout>; + using dist = uint64_t; + + const clk_lyt layout{aspect_ratio{5, 5}, use_clocking()}; + + BENCHMARK("smart_distance_cache (cold start)") + { + const auto dist_map_func = smart_distance_cache_functor{layout, &a_star_distance}; + + return sum_distances(layout, dist_map_func); + }; + + // warm up the cache + const auto dist_map_func = smart_distance_cache_functor{layout, &a_star_distance}; + sum_distances(layout, dist_map_func); + + BENCHMARK("smart_distance_cache (warm start)") + { + return sum_distances(layout, dist_map_func); + }; +}