Skip to content

Commit

Permalink
Add simple minimally working metrics (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
Morwenn committed Aug 6, 2023
1 parent 1f5f54d commit e2a8f68
Show file tree
Hide file tree
Showing 22 changed files with 1,331 additions and 43 deletions.
58 changes: 58 additions & 0 deletions benchmarks/benchmarking-tools/cpu_cycles.h
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2023 Morwenn
* SPDX-License-Identifier: MIT
*/
#include <cstdint>
#include <utility>
#include <cpp-sort/utility/adapter_storage.h>
#include <cpp-sort/utility/metrics_tools.h>
#include <cpp-sort/detail/checkers.h>
#include "rdtsc.h"

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

struct cpu_cycles_tag {};

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

template<typename Sorter>
struct cpu_cycles:
cppsort::utility::adapter_storage<Sorter>,
cppsort::detail::check_iterator_category<Sorter>,
cppsort::detail::check_is_always_stable<Sorter>
{
using tag_t = cpu_cycles_tag;
using metric_t = cppsort::utility::metric<unsigned long long, tag_t>;

cpu_cycles() = default;

constexpr explicit cpu_cycles(Sorter sorter):
cppsort::utility::adapter_storage<Sorter>(std::move(sorter))
{}

template<typename... Args>
auto operator()(Args&&... args) const
-> decltype(
this->get()(std::forward<Args>(args)...),
metric_t(std::declval<unsigned long long>())
)
{
auto start = ::rdtsc();
this->get()(std::forward<Args>(args)...);
auto stop = ::rdtsc();
return metric_t(stop - start);
}
};

////////////////////////////////////////////////////////////
// is_stable specialization

namespace cppsort
{
template<typename Sorter, typename... Args>
struct is_stable<cpu_cycles<Sorter>(Args...)>:
is_stable<Sorter(Args...)>
{};
}
4 changes: 3 additions & 1 deletion benchmarks/benchmarking-tools/rdtsc.h
Expand Up @@ -26,7 +26,9 @@

#ifdef _WIN32
#include <intrin.h>
#define rdtsc __rdtsc
inline unsigned long long rdtsc() {
return __rdtsc();
}
#else
#ifdef __i586__
static __inline__ unsigned long long rdtsc() {
Expand Down
12 changes: 6 additions & 6 deletions benchmarks/errorbar-plot/bench.cpp
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2021 Morwenn
* Copyright (c) 2020-2023 Morwenn
* SPDX-License-Identifier: MIT
*/
#include <algorithm>
Expand All @@ -16,6 +16,7 @@
#include <string>
#include <utility>
#include <vector>
#include <cpp-sort/metrics/running_time.h>
#include <cpp-sort/sorters.h>
#include "../benchmarking-tools/distributions.h"
#include "../benchmarking-tools/filesystem.h"
Expand All @@ -36,7 +37,7 @@ using sort_f = void (*)(collection_t&);
std::pair<std::string, sort_f> sorts[] = {
{ "heap_sort", cppsort::heap_sort },
{ "poplar_sort", cppsort::poplar_sort },
{ "smooth_sort", cppsort::smooth_sort }
{ "smooth_sort", cppsort::smooth_sort },
};

// Distribution to benchmark against
Expand Down Expand Up @@ -95,11 +96,10 @@ int main(int argc, char** argv)
while (total_end - total_start < max_run_time && times.size() < max_runs_per_size) {
collection_t collection;
distribution(std::back_inserter(collection), size);
auto start = clock_type::now();
sort.second(collection);
auto end = clock_type::now();
auto do_sort = cppsort::metrics::running_time<sort_f>(sort.second);
auto duration = do_sort(collection);
assert(std::is_sorted(std::begin(collection), std::end(collection)));
times.push_back(std::chrono::duration<double, std::milli>(end - start).count());
times.push_back(duration.value().count());
total_end = clock_type::now();
}

Expand Down
21 changes: 10 additions & 11 deletions benchmarks/inversions/inv-bench.cpp
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2022 Morwenn
* Copyright (c) 2020-2023 Morwenn
* SPDX-License-Identifier: MIT
*/
#include <cassert>
Expand All @@ -13,10 +13,11 @@
#include <string>
#include <utility>
#include <vector>
#include <cpp-sort/adapters.h>
#include <cpp-sort/sorters.h>
#include "../benchmarking-tools/cpu_cycles.h"
#include "../benchmarking-tools/distributions.h"
#include "../benchmarking-tools/filesystem.h"
#include "../benchmarking-tools/rdtsc.h"

using namespace std::chrono_literals;

Expand All @@ -31,9 +32,9 @@ using collection_t = std::vector<value_t>;
// Sorting algorithms to benchmark
using sort_f = void (*)(collection_t&);
std::pair<std::string, sort_f> sorts[] = {
{ "drop_merge_sort", cppsort::drop_merge_sort },
{ "pdq_sort", cppsort::pdq_sort },
{ "split_sort", cppsort::split_sort },
{ "heap_sort", cppsort::heap_sort },
{ "drop_merge_adapter(heap_sort)", cppsort::drop_merge_adapter<cppsort::heap_sorter>{} },
{ "split_adapter(heap_sort)", cppsort::split_adapter<cppsort::heap_sorter>{} },
};

// Size of the collections to sort
Expand Down Expand Up @@ -92,15 +93,13 @@ int main(int argc, char* argv[])

auto total_start = clock_type::now();
auto total_end = clock_type::now();
while (std::chrono::duration_cast<std::chrono::seconds>(total_end - total_start) < max_run_time &&
cycles.size() < max_runs_per_size) {
while (total_end - total_start < max_run_time && cycles.size() < max_runs_per_size) {
collection_t collection;
distribution(std::back_inserter(collection), size);
std::uint64_t start = rdtsc();
sort.second(collection);
std::uint64_t end = rdtsc();
auto do_sort = cpu_cycles<sort_f>(sort.second);
auto nb_cycles = do_sort(collection);
assert(std::is_sorted(std::begin(collection), std::end(collection)));
cycles.push_back(double(end - start) / size + 0.5);
cycles.push_back(double(nb_cycles.value()) / size + 0.5);
total_end = clock_type::now();
}

Expand Down
15 changes: 7 additions & 8 deletions benchmarks/patterns/bench.cpp
Expand Up @@ -36,8 +36,8 @@
#include <utility>
#include <vector>
#include <cpp-sort/sorters.h>
#include "../benchmarking-tools/cpu_cycles.h"
#include "../benchmarking-tools/distributions.h"
#include "../benchmarking-tools/rdtsc.h"

// Type of data to sort during the benchmark
using value_t = double;
Expand Down Expand Up @@ -94,26 +94,25 @@ int main()
distributions_prng.seed(seed);

for (auto size: sizes) {
std::vector<std::uint64_t> cycles;
std::vector<std::uint64_t> cycles_per_element;

auto total_start = clock_type::now();
auto total_end = clock_type::now();
while (total_end - total_start < 5s) {
collection_t collection;
distribution.second(std::back_inserter(collection), size);
std::uint64_t start = rdtsc();
sort.second(collection);
std::uint64_t end = rdtsc();
auto do_sort = cpu_cycles<sort_f>(sort.second);
auto nb_cycles = do_sort(collection);
assert(std::is_sorted(std::begin(collection), std::end(collection)));
cycles.push_back(double(end - start) / size + 0.5);
cycles_per_element.push_back(double(nb_cycles.value()) / size + 0.5);
total_end = clock_type::now();
}

for (std::ostream* stream: {&std::cout, &std::cerr}) {
(*stream) << size << ", " << distribution.first << ", " << sort.first << ", ";
auto it = cycles.begin();
auto it = cycles_per_element.begin();
(*stream) << *it;
while (++it != cycles.end()) {
while (++it != cycles_per_element.end()) {
(*stream) << ", " << *it;
}
(*stream) << std::endl;
Expand Down
12 changes: 5 additions & 7 deletions benchmarks/small-array/benchmark.cpp
Expand Up @@ -16,8 +16,8 @@
#include <cpp-sort/adapters.h>
#include <cpp-sort/fixed_sorters.h>
#include <cpp-sort/sorters.h>
#include "../benchmarking-tools/cpu_cycles.h"
#include "../benchmarking-tools/distributions.h"
#include "../benchmarking-tools/rdtsc.h"

using namespace std::chrono_literals;

Expand Down Expand Up @@ -59,15 +59,13 @@ auto time_it(Sorter sorter, Dist distribution)
// Generate and sort arrays of size N thanks to distribution
auto total_start = clock_type::now();
auto total_end = clock_type::now();
while (std::chrono::duration_cast<std::chrono::seconds>(total_end - total_start) < max_run_time &&
cycles.size() < max_runs_per_size) {
while (total_end - total_start < max_run_time && cycles.size() < max_runs_per_size) {
std::array<T, N> arr;
distribution(arr.begin(), N);
std::uint64_t start = rdtsc();
sorter(arr);
std::uint64_t end = rdtsc();
auto do_sort = cpu_cycles<Sorter>(sorter);
auto nb_cycles = do_sort(arr);
assert(std::is_sorted(arr.begin(), arr.end()));
cycles.push_back(double(end - start) / N);
cycles.push_back(double(nb_cycles.value()) / N);
total_end = clock_type::now();
}

Expand Down
3 changes: 3 additions & 0 deletions docs/Library-nomenclature.md
Expand Up @@ -28,6 +28,8 @@

auto max_inversion = cppsort::probe::dis(collection);

* *Metric*: as special kind of *sorter adapter* that returns information about sorted collections. See [the corresponding page][metrics] for additional information.

* *Projection*: some sorters accept a projection as an additional parameter. A projection is a unary function that allows to "view" the values of a collection differently. For example it may allow to sort a collection of values on a specific field. The available sorting algorithms transform projections on the fly so that pointers to member data can also be used as projections. Projections were pioneered by the [Adobe Source Libraries][stlab] and appear in the C++20 [constrained algorithms][std-ranges].

struct wrapper { int value; };
Expand Down Expand Up @@ -74,6 +76,7 @@
[iterator-category]: Sorter-traits.md#iterator_category
[iterator-tags]: https://en.cppreference.com/w/cpp/iterator/iterator_tags
[measures-of-presortedness]: Measures-of-presortedness.md
[metrics]: Metrics.md
[p0022]: https://wg21.link/P0022
[radix-sort]: https://en.wikipedia.org/wiki/Radix_sort
[sorter-adapters]: Sorter-adapters.md
Expand Down
82 changes: 82 additions & 0 deletions docs/Metrics.md
@@ -0,0 +1,82 @@
*New in version 1.15.0*

*Metrics* are special kinds of [*sorter adapters*][sorter-adapters] that can be used to compute properties about a sorting algorithm, such as the number of comparisons performed while sorting a given collection, or the time it took to sort. As *sorter adapters*, metrics follow the *unified sorting interface*, and *do* sort the passed collection before returning the result.

The anatomy of a metric adapter is thus close to that of any *sorter adapter* with the following additional properties:
* The result of the *adapted sorter*'s is replaced with an instance of [`cppsort:::utility::metric`][utility-metrics-tools], which is a small wrapper type encapsulating a value.
* It exposes a `tag_t` aliasing the tag of the returned metric value.
* It exposes a `metric_t` type aliasing the returned metric type.

The metrics tag can be any type, generally ending with the `_tag` suffix, and can be either empty or contain freeform static metadata about the kind of metric that uses it. Future versions of **cpp-sort** might standardize some tag fields.

## Available metrics

All available metrics live in the subnamespace `cppsort::metrics`. Even though all of them are available in their own standalone header, it is possible to include all of them at once with the following include:

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

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

### `comparisons`

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

Computes the number of comparisons performed by the *adapted sorter*.

This is done by wrapping the passed comparison function. As such it only works with sorters that accept a comparison, and bypasses components that special-case specific comparison objects.

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

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

### `projections`

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

Computes the number of projections performed by the *adapted sorter*.

This is done by wrapping the passed projection function. As such it only works with sorters that accept a projection, and bypasses components that special-case specific projection objects.

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

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

### `running_time`

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

Computes the time it takes for the *adapted sorter* to sort a collection, using a steady clock by default.

```cpp
template<
typename Sorter,
typename DurationType = typename std::chrono::steady_clock::duration
>
struct running_time;
```

Returns an instance of `utility::metric<DurationType, running_type_tag>`.


[sorter-adapters]: Sorter-adapters.md
[utility-metrics-tools]: Miscellaneous-utilities.md#metrics-tools

0 comments on commit e2a8f68

Please sign in to comment.