Skip to content

Commit

Permalink
New metric: moves (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
Morwenn committed Sep 6, 2023
1 parent 097b085 commit af41365
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 0 deletions.
27 changes: 27 additions & 0 deletions docs/Metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ All available metrics live in the subnamespace `cppsort::metrics`. Even though a

All of the metrics headers also includes `<cpp-sort/utility/metrics_tools.h>`.

*Warning: none of these metrics are thread-safe.*

### `comparisons`

```cpp
Expand All @@ -39,6 +41,31 @@ struct comparisons;

Returns an instance of `utility::metric<CountType, comparisons_tag>`.

### `moves`

```cpp
#include <cpp-sort/metrics/moves.h>
```

Computes the number of moves performed by the *adapted sorter*: it takes both the number of calls to the move constructor and to the move assignment operator of an object into account. A swap operation is considered equivalent to three moves.

The tool currently works by creating a vector of a wrapper type which counts its moves, and moving the sorted contents back to the original collections, which the following implications:
* Sorters that behave differently depending on the iterator type always return the number of moves they perform when sorting random-access iterators.
* Sorters that call operations specific to some types might return a result that is not representative of how they actually perform: this is due to the wrapper not benefiting from the specializations.
* Projection support is mandatory: `metrics::moves` passes a projection to the sorter in order to convert the wrapper type to its underlying type.

```cpp
template<
typename Sorter,
typename CountType = std::size_t
>
struct moves;
```

Returns an instance of `utility::metric<CountType, moves_tag>`.

*New in version 1.16.0*

### `projections`

```cpp
Expand Down
2 changes: 2 additions & 0 deletions include/cpp-sort/fwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ namespace cppsort
template<typename Sorter, typename CountType=std::size_t>
struct comparisons;
template<typename Sorter, typename CountType=std::size_t>
struct moves;
template<typename Sorter, typename CountType=std::size_t>
struct projections;
template<
typename Sorter,
Expand Down
1 change: 1 addition & 0 deletions include/cpp-sort/metrics.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <cpp-sort/utility/metrics_tools.h>

#include <cpp-sort/metrics/comparisons.h>
#include <cpp-sort/metrics/moves.h>
#include <cpp-sort/metrics/running_time.h>
#include <cpp-sort/metrics/projections.h>

Expand Down
191 changes: 191 additions & 0 deletions include/cpp-sort/metrics/moves.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/*
* Copyright (c) 2023 Morwenn
* SPDX-License-Identifier: MIT
*/
#ifndef CPPSORT_METRICS_MOVES_H_
#define CPPSORT_METRICS_MOVES_H_

////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <functional>
#include <utility>
#include <cpp-sort/fwd.h>
#include <cpp-sort/sorter_facade.h>
#include <cpp-sort/sorter_traits.h>
#include <cpp-sort/utility/adapter_storage.h>
#include <cpp-sort/utility/metrics_tools.h>
#include <cpp-sort/utility/size.h>
#include "../detail/checkers.h"
#include "../detail/immovable_vector.h"
#include "../detail/iterator_traits.h"
#include "../detail/type_traits.h"

namespace cppsort
{
namespace metrics
{
namespace detail
{
template<typename T, typename CountType>
struct move_counting_wrapper
{
// Wrapped value, readily accessible
T value;
// Moves counter
CountType* count;

move_counting_wrapper(T&& value, CountType& counter):
value(std::move(value)),
count(&counter)
{
// Do not increment the counter when constructing from value
}

move_counting_wrapper(move_counting_wrapper&& other):
value(std::move(other.value)),
count(other.count)
{
++(*count);
}

auto operator=(move_counting_wrapper&& other)
-> move_counting_wrapper&
{
value = std::move(other.value);
count = other.count;
++(*count);
return *this;
}

// Not copyable
move_counting_wrapper(const move_counting_wrapper&) = delete;
move_counting_wrapper& operator=(const move_counting_wrapper&) = delete;
};

template<typename CountType, typename ForwardIterator, typename Size,
typename Compare, typename Projection, typename Sorter>
auto count_moves(ForwardIterator first, ForwardIterator last, Size size,
Compare compare, Projection projection, Sorter&& sorter)
-> CountType
{
using utility::iter_move;
using wrapper_t = move_counting_wrapper<
cppsort::detail::rvalue_type_t<ForwardIterator>,
CountType
>;

// Use an immovable_vector to ensure that no move is performed
// hile filling the vector
CountType count(0);
cppsort::detail::immovable_vector<wrapper_t> vec(size);
for (auto it = first; it != last; ++it) {
vec.emplace_back(iter_move(it), count);
}

std::forward<Sorter>(sorter)(
vec.begin(), vec.end(), std::move(compare),
utility::as_projection(&wrapper_t::value) | std::move(projection)
);

auto vec_it = vec.begin();
for (auto it = first; it != last; ++it, ++vec_it) {
*it = std::move(vec_it->value);
}

return count;
}
}

////////////////////////////////////////////////////////////
// Tag

struct moves_tag {};

////////////////////////////////////////////////////////////
// Metric

namespace detail
{
template<typename Sorter, typename CountType>
struct moves_impl:
utility::adapter_storage<Sorter>,
cppsort::detail::check_iterator_category<Sorter>,
cppsort::detail::check_is_always_stable<Sorter>
{
using tag_t = moves_tag;
using metric_t = utility::metric<CountType, tag_t>;

moves_impl() = default;

constexpr explicit moves_impl(Sorter&& sorter):
utility::adapter_storage<Sorter>(std::move(sorter))
{}

template<
typename ForwardIterator,
typename Compare = std::less<>,
typename Projection = utility::identity,
typename = cppsort::detail::enable_if_t<is_projection_iterator_v<
Projection, ForwardIterator, Compare
>>
>
auto operator()(ForwardIterator first, ForwardIterator last,
Compare compare={}, Projection projection={}) const
-> metric_t
{
auto count = detail::count_moves<CountType>(
first, last,
std::distance(first, last),
std::move(compare), std::move(projection),
this->get()
);
return metric_t(count);
}

template<
typename ForwardIterable,
typename Compare = std::less<>,
typename Projection = utility::identity,
typename = cppsort::detail::enable_if_t<
is_projection_v<Projection, ForwardIterable, Compare>
>
>
auto operator()(ForwardIterable&& iterable,
Compare compare={}, Projection projection={}) const
{
auto count = detail::count_moves<CountType>(
std::begin(iterable), std::end(iterable),
utility::size(iterable),
std::move(compare), std::move(projection),
this->get()
);
return metric_t(count);
}
};
}

template<typename Sorter, typename CountType>
struct moves:
sorter_facade<detail::moves_impl<Sorter, CountType>>
{
moves() = default;

constexpr explicit moves(Sorter sorter):
sorter_facade<detail::moves_impl<Sorter, CountType>>(std::move(sorter))
{}
};
}}

namespace cppsort
{
////////////////////////////////////////////////////////////
// is_stable specialization

template<typename Sorter, typename CountType, typename... Args>
struct is_stable<metrics::moves<Sorter, CountType>(Args...)>:
is_stable<Sorter(Args...)>
{};
}

#endif // CPPSORT_METRICS_MOVES_H_
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ add_executable(main-tests

# Metrics tests
metrics/comparisons.cpp
metrics/moves.cpp
metrics/projections.cpp
metrics/running_time.cpp

Expand Down
76 changes: 76 additions & 0 deletions tests/metrics/moves.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2023 Morwenn
* SPDX-License-Identifier: MIT
*/
#include <algorithm>
#include <cstddef>
#include <functional>
#include <iterator>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <cpp-sort/metrics/moves.h>
#include <cpp-sort/sorters/heap_sorter.h>
#include <testing-tools/algorithm.h>
#include <testing-tools/distributions.h>
#include <testing-tools/span.h>
#include <testing-tools/wrapper.h>

using wrapper = generic_wrapper<int>;

TEST_CASE( "basic metrics::moves tests",
"[metrics][heap_sorter]" )
{
cppsort::metrics::moves<cppsort::heap_sorter> sorter;

SECTION( "without projections" )
{
std::vector<int> collection;
auto distribution = dist::descending_plateau{};
distribution(std::back_inserter(collection), 65);

auto res = sorter(collection);
CHECK( res == 463 );
CHECK( std::is_sorted(collection.begin(), collection.end()) );
}

SECTION( "with projections" )
{
std::vector<wrapper> collection;
auto distribution = dist::descending_plateau{};
distribution(std::back_inserter(collection), 80);

auto res = sorter(collection, &wrapper::value);
CHECK( res == 573 );
CHECK( helpers::is_sorted(collection.begin(), collection.end(),
std::less<>{}, &wrapper::value) );
}
}

TEST_CASE( "metrics::moves with span",
"[metrics][span][heap_sorter]" )
{
cppsort::metrics::moves<cppsort::heap_sorter> sorter;

SECTION( "without projections" )
{
std::vector<int> collection;
auto distribution = dist::descending_plateau{};
distribution(std::back_inserter(collection), 65);

auto res = sorter(make_span(collection));
CHECK( res == 463 );
CHECK( std::is_sorted(collection.begin(), collection.end()) );
}

SECTION( "with projections" )
{
std::vector<wrapper> collection;
auto distribution = dist::descending_plateau{};
distribution(std::back_inserter(collection), 80);

auto res = sorter(make_span(collection), &wrapper::value);
CHECK( res == 573 );
CHECK( helpers::is_sorted(collection.begin(), collection.end(),
std::less<>{}, &wrapper::value) );
}
}

0 comments on commit af41365

Please sign in to comment.