Commit 01518ab1 authored by Edwin Carlinet's avatar Edwin Carlinet
Browse files

Added vector impl of hierarchical queues + benchmark.

parent 9fcc818e
Pipeline #15487 passed with stages
in 10 minutes and 51 seconds
#include <mln/core/algorithm/transform.hpp>
#include <mln/core/colors.hpp>
#include <mln/core/image/experimental/ndimage.hpp>
#include <mln/core/neighborhood/c4.hpp>
#include <mln/core/neighb2d.hpp>
#include <mln/io/experimental/imread.hpp>
#include <mln/morpho/experimental/maxtree.hpp>
#include <mln/morpho/maxtree/maxtree.hpp>
#include <benchmark/benchmark.h>
#include <fixtures/ImagePath/image_path.hpp>
class BMMorpho : public benchmark::Fixture
{
public:
using image_t = mln::experimental::image2d<uint8_t>;
using image_ref_t = mln::image2d<uint8_t>;
BMMorpho()
{
if (!g_loaded)
{
const char* filename = "Aerial_view_of_Olbia.jpg";
mln::experimental::image2d<mln::rgb8> input;
mln::io::experimental::imread(filename, input);
g_input = mln::transform(input, [](mln::rgb8 x) -> uint8_t { return x[0]; });
g_loaded = true;
}
m_input = g_input;
int nr = m_input.width();
int nc = m_input.height();
mln::resize(m_output, m_input);
m_size = nr * nc;
m_input.to(m_input_, false);
m_output.to(m_output_, false);
}
void run(benchmark::State& st, std::function<void(const image_t& input)> callback)
{
for (auto _ : st)
callback(m_input);
st.SetBytesProcessed(int64_t(st.iterations()) * int64_t(m_size));
}
void run2(benchmark::State& st, std::function<void(const image_ref_t& input)> callback)
{
for (auto _ : st)
callback(m_input_);
st.SetBytesProcessed(int64_t(st.iterations()) * int64_t(m_size));
}
protected:
static bool g_loaded;
static mln::experimental::image2d<uint8_t> g_input;
mln::experimental::image2d<uint8_t> m_input;
mln::experimental::image2d<uint8_t> m_output;
std::size_t m_size;
mln::image2d<uint8_t> m_input_;
mln::image2d<uint8_t> m_output_;
};
bool BMMorpho::g_loaded = false;
mln::experimental::image2d<uint8_t> BMMorpho::g_input;
BENCHMARK_F(BMMorpho, MaxtreeNew)(benchmark::State& st)
{
auto f = [](const image_t& input) { mln::morpho::experimental::maxtree(input, mln::experimental::c4); };
this->run(st, f);
}
BENCHMARK_F(BMMorpho, MaxtreeRef)(benchmark::State& st)
{
auto f = [](const image_ref_t& input) { mln::morpho::maxtree_indexes(input, mln::c4); };
this->run2(st, f);
}
BENCHMARK_MAIN();
......@@ -66,6 +66,7 @@ set_source_files_properties(${src_standalone} PROPERTIES COMPILE_FLAGS ${STANDAL
add_benchmark(BMAlgorithms BMAlgorithms.cpp BMAlgorithms_main.cpp)
add_benchmark(BMNeighborhood BMNeighborhood.cpp BMNeighborhood_main.cpp)
add_benchmark(BMRotation BMRotation.cpp)
add_benchmark(BMMorphoMaxtree BMMorphoMaxtree.cpp)
add_benchmark(BMMorphoBase BMMorphoBase.cpp)
add_benchmark(BMMorphers BMMorphers.cpp BMMorphers_main.cpp)
add_benchmark(BMReference_Linear BMReference_Linear.cpp BMReference_Linear_Reversed.cpp BMReference_Linear_main.cpp)
......
......@@ -63,6 +63,7 @@ target_sources(Pylene PRIVATE
src/io/imprint.cpp
src/morpho/maxtree.cpp
src/morpho/component_tree.cpp
src/morpho/hvector.cpp
)
# Compiler configurations
......
#pragma once
#include <mln/core/algorithm/for_each.hpp>
#include <vector>
#include <concepts/concepts.hpp>
#include <range/v3/algorithm/fill.hpp>
#include <range/v3/view/span.hpp>
namespace mln::data::experimental
{
template <class I, class OutputRng>
[[gnu::noinline]]
void histogram(I image, OutputRng histogram);
template <class I>
[[gnu::noinline]]
std::vector<std::size_t> histogram(I image);
/******************************************/
/**** Implementation ****/
/******************************************/
namespace impl
{
template <class I, class OutputRng>
void histogram(I&& image, OutputRng hist)
{
using V = image_value_t<std::remove_reference_t<I>>;
static_assert(::concepts::unsigned_integral<V>);
::ranges::fill(hist, 0);
mln::for_each(std::forward<I>(image), [&hist](int x) { hist[x]++; });
}
}
template <class I, class OutputRng>
[[gnu::noinline]]
void histogram(I image, OutputRng histogram)
{
impl::histogram(image, histogram);
}
template <class I>
[[gnu::noinline]]
std::vector<std::size_t> histogram(I image)
{
using V = image_value_t<I>;
static_assert(::concepts::unsigned_integral<V>);
std::vector<std::size_t> histogram;
histogram.resize(std::numeric_limits<V>::max() + 1);
impl::histogram(image, ::ranges::make_span(histogram.data(), histogram.size()));
return histogram;
}
}
......@@ -34,7 +34,12 @@ namespace mln::morpho::experimental::canvas
template <class I, class N, class DFVisitor, class Proj>
void depthfirst(I& f, N nbh, DFVisitor& viz, image_point_t<I> start)
{
enum st { NONE = 0, INQUEUE = 1, DONE = 2 };
enum st
{
NONE = 0,
INQUEUE = 1,
DONE = 255,
};
using P = image_point_t<I>;
//using V = image_value_t<I>;
......@@ -52,7 +57,8 @@ namespace mln::morpho::experimental::canvas
status.extension().fill(DONE);
mln::morpho::experimental::details::pqueue_fifo<I> queue(f);
constexpr auto impl_type = mln::morpho::experimental::details::pqueue_impl::vector;
mln::morpho::experimental::details::pqueue_fifo<I, impl_type> queue(f);
queue.push(f(start), start);
status(start) = INQUEUE;
......@@ -62,33 +68,39 @@ namespace mln::morpho::experimental::canvas
{
P p = start;
auto current_level = f(start);
int pstatus;
flood:
viz.on_flood_start(current_level, p);
pstatus = 0;
keep_flooding:
for (auto n : nbh(p))
for (int k = 1; auto n : nbh(p))
{
if (status.at(n) != NONE)
int mask = 1 << k++;
if ((pstatus & mask) || status.at(n) != NONE)
continue;
// Insert n INQUEUE
auto nval = f(n);
status(n) = INQUEUE;
queue.push(nval, n);
pstatus |= mask;
// If the neighbor is lower, postpone the neighbor
if (nval <= current_level)
continue;
// Otherwise, process it, (do not remove p from stack)
status(p) = pstatus;
current_level = nval;
p = n;
goto flood;
}
// All the neighbors have been seen, p is DONE
status(p) = DONE;
// status(p) = DONE;
viz.on_done(current_level, p);
queue.pop();
......@@ -99,6 +111,7 @@ namespace mln::morpho::experimental::canvas
{
auto old_level = current_level;
std::tie(current_level, p) = queue.top();
pstatus = status(p);
if (current_level == old_level)
goto keep_flooding;
viz.on_flood_end(old_level, current_level);
......
......@@ -14,6 +14,7 @@ namespace mln::morpho::experimental
{
template <class I, class N>
[[gnu::noinline]] //
std::pair<component_tree<image_value_t<I>>, image_ch_value_t<I, component_tree<>::node_id_type>> //
maxtree(I input, N nbh);
......@@ -132,6 +133,7 @@ namespace mln::morpho::experimental
} // namespace details
template <class I, class N>
[[gnu::noinline]] //
std::pair<component_tree<image_value_t<I>>, image_ch_value_t<I, component_tree<>::node_id_type>> //
maxtree(I input, N nbh)
{
......
#pragma once
#include <mln/core/concept/new/images.hpp>
#include <mln/core/assert.hpp>
namespace mln::morpho::experimental::details
{
/// \brief Set of points ordered hierarchically into a list of points
///
/// \tparam N Number of levels
/// \tparam P Type of point
/// \tparam LinkImage Type of the link image
template <int N, class P, class LinkImage>
class hlinked_lists
{
static_assert(N <= (1 << 24), "Too many numbers of level");
static_assert(std::is_convertible_v<P, image_point_t<LinkImage>>);
template <class J>
hlinked_lists(J&& f);
void push_front(int level, P p) noexcept;
void push_back(int level, P p) noexcept;
P pop_front(int level) noexcept;
P back(int level) const noexcept;
P front(int level) const noexcept;
bool empty(int level) const noexcept;
private:
struct node_t
{
P head;
P tail;
int size = 0;
};
std::array<node_t, N> m_lists;
LinkImage m_next;
};
/******************************************/
/**** Implementation ****/
/******************************************/
template <int N, class P, class LinkImage>
template <class J>
inline hlinked_lists<N, P, LinkImage>::hlinked_lists(J&& f)
: m_next(std::forward<J>(f), image_build_params{})
{
}
template <int N, class P, class LinkImage>
inline bool hlinked_lists<N, P, LinkImage>::empty(int level) const noexcept
{
mln_precondition(level < N);
return m_lists[level].size == 0;
}
template <int N, class P, class LinkImage>
inline void hlinked_lists<N, P, LinkImage>::push_front(int level, P p) noexcept
{
mln_precondition(level < N);
if (m_lists[level].size == 0)
{
m_lists[level].head = p;
m_lists[level].tail = p;
}
else
{
m_next(p) = m_lists[level].head;
m_lists[level].head = p;
}
m_lists[level].size++;
}
template <int N, class P, class LinkImage>
inline void hlinked_lists<N, P, LinkImage>::push_back(int level, P p) noexcept
{
mln_precondition(level < N);
if (m_lists[level].size == 0)
{
m_lists[level].head = p;
m_lists[level].tail = p;
}
else
{
m_next(m_lists[level].tail) = p;
m_lists[level].tail = p;
}
m_lists[level].size++;
}
template <int N, class P, class LinkImage>
inline P hlinked_lists<N, P, LinkImage>::pop_front(int level) noexcept
{
mln_precondition(level < N);
mln_precondition(m_lists[level].size > 0 && "Empty list");
P head = m_lists[level].head;
m_lists[level].head = m_next(head);
m_lists[level].size--;
return head;
}
template <int N, class P, class LinkImage>
inline P hlinked_lists<N, P, LinkImage>::front(int level) const noexcept
{
mln_precondition(level < N);
mln_precondition(m_lists[level].size > 0 && "Empty list");
return m_lists[level].head;
}
template <int N, class P, class LinkImage>
inline P hlinked_lists<N, P, LinkImage>::back(int level) const noexcept
{
mln_precondition(level < N);
mln_precondition(m_lists[level].size > 0 && "Empty list");
return m_lists[level].tail;
}
} // namespace mln::morpho::experimental::detail
#pragma once
#include <mln/morpho/experimental/private/hvector.hpp>
#include <mln/morpho/experimental/private/hlinked_lists.hpp>
namespace mln::morpho::experimental::detail
{
......@@ -12,15 +12,15 @@ namespace mln::morpho::experimental::detail
/// \tparam N Number of levels
/// \tparam P Type of point
/// \tparam LinkImage Type of the link image
template <int N, class P, class LinkImage, bool reverse>
class hpqueue : private hlinked_lists<N, P, LinkImage>
template <int N, class P, class Impl, bool reverse>
class hpqueue : private Impl
{
using base = hlinked_lists<N, P, LinkImage>;
using base = Impl;
public:
template <class J>
hpqueue(J&& f)
: hlinked_lists<N, P, LinkImage>(std::forward<J>(f))
: base(std::forward<J>(f))
{
}
......@@ -36,7 +36,10 @@ namespace mln::morpho::experimental::detail
m_current_level = std::max(m_current_level, level);
}
// Push in first position at givel level
// Not supported by all implementation
/*
void push_first(int level, P p) noexcept
{
mln_precondition(0 <= level && level < N);
......@@ -47,6 +50,7 @@ namespace mln::morpho::experimental::detail
else
m_current_level = std::max(m_current_level, level);
}
*/
bool has_key(int level) const noexcept { return !base::empty(level); }
bool empty() const noexcept { return base::empty(m_current_level); }
......
#pragma once
#include <mln/core/assert.hpp>
#include <mln/core/concept/new/images.hpp>
#include <mln/data/experimental/histogram.hpp>
#include <algorithm>
#include <numeric>
#include <mln/core/assert.hpp>
namespace mln::morpho::experimental::detail
namespace mln::morpho::experimental::details
{
/// \brief Set of points ordered hierarchically into a list of points
/// \brief Set of points ordered hierarchically into a list of points (vector implemented)
///
/// \tparam N Number of levels
/// \tparam P Type of point
/// \tparam LinkImage Type of the link image
template <int N, class P, class LinkImage>
class hlinked_lists
template <class P>
class hvectors;
/// \brief Base implementation
template <>
class hvectors<void>
{
static_assert(N <= (1 << 24), "Too many numbers of level");
static_assert(std::is_convertible_v<P, image_point_t<LinkImage>>);
public:
bool empty(int level) const noexcept;
protected:
/// \param count Number of elements in the histogram
/// \param size Size in bytes of an element
hvectors() = default;
~hvectors();
void init(const std::size_t* cumulated_histogram, std::size_t count, std::size_t size);
struct vector_t { void* begin; void* end; };
vector_t* m_lists; /* List of unitialized storage */
};
template <class P>
class hvectors : public hvectors<void>
{
template <class J>
hlinked_lists(J&& f);
hvectors(J&& f);
~hvectors() = default;
hvectors(const hvectors&) = delete;
hvectors(hvectors&&) = delete;
hvectors& operator= (const hvectors&) = delete;
hvectors& operator= (hvectors&&) = delete;
void push_front(int level, P p) noexcept;
void push_back(int level, P p) noexcept;
P pop_back(int level) noexcept;
P pop_front(int level) noexcept;
P back(int level) const noexcept;
P front(int level) const noexcept;
bool empty(int level) const noexcept;
};
private:
struct node_t
{
P head;
P tail;
int size = 0;
};
std::array<node_t, N> m_lists;
LinkImage m_next;
};
/******************************************/
/**** Implementation ****/
/******************************************/
template <int N, class P, class LinkImage>
template <class P>
template <class J>
inline hlinked_lists<N, P, LinkImage>::hlinked_lists(J&& f)
: m_next(std::forward<J>(f), image_build_params{})
inline hvectors<P>::hvectors(J&& f)
{
auto hist = mln::data::experimental::histogram(std::forward<J>(f));
std::partial_sum(hist.begin(), hist.end(), hist.begin());
this->init(hist.data(), hist.size(), sizeof(P));
}
template <int N, class P, class LinkImage>
inline bool hlinked_lists<N, P, LinkImage>::empty(int level) const noexcept
inline bool hvectors<void>::empty(int level) const noexcept
{
mln_precondition(level < N);
return m_lists[level].size == 0;
return m_lists[level].begin == m_lists[level].end;
}
template <int N, class P, class LinkImage>
inline void hlinked_lists<N, P, LinkImage>::push_front(int level, P p) noexcept
template <class P>
inline void hvectors<P>::push_back(int level, P p) noexcept
{
mln_precondition(level < N);
if (m_lists[level].size == 0)
{
m_lists[level].head = p;
m_lists[level].tail = p;
}
else
{
m_next(p) = m_lists[level].head;
m_lists[level].head = p;
}
m_lists[level].size++;
P*& end = reinterpret_cast<P*&>(m_lists[level].end);
new ((void*) end++) P(p);
}
template <int N, class P, class LinkImage>
inline void hlinked_lists<N, P, LinkImage>::push_back(int level, P p) noexcept
template <class P>
inline P hvectors<P>::pop_front(int level) noexcept