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 Dec 31, 2023
1 parent 283279d commit a53a0e2
Show file tree
Hide file tree
Showing 24 changed files with 1,637 additions and 75 deletions.
62 changes: 62 additions & 0 deletions benchmarks/benchmarking-tools/cpu_cycles.h
@@ -0,0 +1,62 @@
/*
* 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>,
cppsort::detail::sorter_facade_fptr<
cpu_cycles<Sorter>,
std::is_empty_v<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...)>
{};
}
6 changes: 4 additions & 2 deletions benchmarks/benchmarking-tools/rdtsc.h
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015-2020 Morwenn
* Copyright (c) 2015-2023 Morwenn
* SPDX-License-Identifier: MIT
*/

Expand All @@ -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
31 changes: 11 additions & 20 deletions benchmarks/patterns/bench.cpp
Expand Up @@ -32,12 +32,11 @@
#include <iostream>
#include <iterator>
#include <string>
#include <type_traits>
#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 All @@ -46,19 +45,12 @@ using collection_t = std::vector<value_t>;

// Handy function pointer aliases
using distr_f = void (*)(std::back_insert_iterator<collection_t>, long long int);
using sort_f = void (*)(collection_t::iterator, collection_t::iterator);
using sort_f = void (*)(collection_t&);

int main()
{
using namespace std::chrono_literals;

// Always use a steady clock
using clock_type = std::conditional_t<
std::chrono::high_resolution_clock::is_steady,
std::chrono::high_resolution_clock,
std::chrono::steady_clock
>;

std::pair<std::string, distr_f> distributions[] = {
{ "shuffled", dist::shuffled() },
{ "shuffled_16_values", dist::shuffled_16_values() },
Expand Down Expand Up @@ -94,26 +86,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();
auto total_start = std::chrono::steady_clock::now();
auto total_end = std::chrono::steady_clock::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.begin(), collection.end());
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);
total_end = clock_type::now();
cycles_per_element.push_back(double(nb_cycles.value()) / size + 0.5);
total_end = std::chrono::steady_clock::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
32 changes: 12 additions & 20 deletions benchmarks/presortedness/bench-presortedness.cpp
Expand Up @@ -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_sort) },
{ "split_adapter(heap_sort)", cppsort::split_adapter(cppsort::heap_sort) },
};

// Size of the collections to sort
Expand All @@ -56,13 +57,6 @@ int main(int argc, char* argv[])
output_directory = argv[1];
}

// Always use a steady clock
using clock_type = std::conditional_t<
std::chrono::high_resolution_clock::is_steady,
std::chrono::high_resolution_clock,
std::chrono::steady_clock
>;

// Poor seed, yet enough for our benchmarks
std::uint_fast32_t seed = std::time(nullptr);
std::cout << "SEED: " << seed << '\n';
Expand Down Expand Up @@ -90,18 +84,16 @@ int main(int argc, char* argv[])

std::vector<std::uint64_t> cycles;

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) {
auto total_start = std::chrono::steady_clock::now();
auto total_end = std::chrono::steady_clock::now();
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);
total_end = clock_type::now();
cycles.push_back(double(nb_cycles.value()) / size + 0.5);
total_end = std::chrono::steady_clock::now();
}

// Compute and display stats & numbers
Expand Down
26 changes: 8 additions & 18 deletions benchmarks/small-array/benchmark.cpp
Expand Up @@ -11,24 +11,16 @@
#include <fstream>
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#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;

// Choose the best clock type (always steady)
using clock_type = std::conditional_t<
std::chrono::high_resolution_clock::is_steady,
std::chrono::high_resolution_clock,
std::chrono::steady_clock
>;

// Maximum time to let the benchmark run for a given size before giving up
auto max_run_time = 5s;
// Maximum number of benchmark runs per size
Expand Down Expand Up @@ -58,18 +50,16 @@ auto time_it(Sorter sorter, Dist distribution)
std::vector<std::uint64_t> cycles;

// 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) {
auto total_start = std::chrono::steady_clock::now();
auto total_end = std::chrono::steady_clock::now();
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);
total_end = clock_type::now();
cycles.push_back(double(nb_cycles.value()) / N);
total_end = std::chrono::steady_clock::now();
}

// Return the median number of cycles per element
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
[modified-std]: Modified-standard-library.md
[radix-sort]: https://en.wikipedia.org/wiki/Radix_sort
[sorter-adapters]: Sorter-adapters.md
Expand Down

0 comments on commit a53a0e2

Please sign in to comment.