Commit 0a1aacf6 authored by Pádraig Ó Conbhuí's avatar Pádraig Ó Conbhuí
Browse files

Add tools, run formatting

parent 52739195
......@@ -7,15 +7,10 @@
#include <chrono>
#include <vector>
TEST_CASE("perforate::scoped_trace", "[scoped_trace][benchmark]")
{
BENCHMARK("std::chrono::high_resolution_clock::now()")
{
return std::chrono::high_resolution_clock::now();
};
TEST_CASE("perforate::scoped_trace", "[scoped_trace][benchmark]") {
BENCHMARK("std::chrono::high_resolution_clock::now()") {
return std::chrono::high_resolution_clock::now();
};
BENCHMARK("scoped_trace overhead")
{
PERFORATE_SCOPED_TRACE(nullptr);
};
BENCHMARK("scoped_trace overhead") { PERFORATE_SCOPED_TRACE(nullptr); };
}
......@@ -20,79 +20,64 @@ namespace detail {
// `type_policy::make()`, unique for each Tag.
// Contains accessors suitable for pre-main avoiding the
// "Static Initialization Order Fiasco" (SIOF)
template<typename type_policy>
class per_tag {
template <typename type_policy> class per_tag {
public:
using policy_type = typename type_policy::type;
private:
// A SIOF-safe holder for a static instance of type_policy.
static type_policy &policy_singleton() {
static type_policy m_policy{};
return m_policy;
}
template <typename Tag> class per_tag_singleton {
public:
using policy_type = typename type_policy::type;
private:
// A SIOF-safe holder for a static instance of type_policy.
static type_policy& policy_singleton()
{
static type_policy m_policy{};
return m_policy;
// SIOF-safe
static policy_type &safe_value() {
static policy_type m_safe_value = policy_singleton().template make<Tag>();
return m_safe_value;
}
template<typename Tag>
class per_tag_singleton {
public:
// SIOF-safe
static policy_type& safe_value()
{
static policy_type m_safe_value =
policy_singleton().template make<Tag>();
return m_safe_value;
}
// SIOF-unsafe, but without extra checking
static policy_type& value;
};
// SIOF-unsafe, but without extra checking
static policy_type &value;
};
public:
// Get an instance of the underlying policy
static const type_policy& policy() { return policy_singleton(); }
//
// Accessors suitable for post-main access, not safe for SIOF situations
//
public:
// Get an instance of the underlying policy
static const type_policy &policy() { return policy_singleton(); }
template<typename Tag>
static policy_type& value()
{
return per_tag_singleton<Tag>::value;
}
//
// Accessors suitable for post-main access, not safe for SIOF situations
//
template<typename Tag>
static policy_type& value(Tag&&)
{
return value<Tag>();
}
template <typename Tag> static policy_type &value() {
return per_tag_singleton<Tag>::value;
}
template <typename Tag> static policy_type &value(Tag &&) {
return value<Tag>();
}
//
// Accessors suitable for pre-main access, safe for SIOF situations
//
//
// Accessors suitable for pre-main access, safe for SIOF situations
//
template<typename Tag>
static policy_type& safe_value()
{
return per_tag_singleton<Tag>::safe_value();
}
template <typename Tag> static policy_type &safe_value() {
return per_tag_singleton<Tag>::safe_value();
}
template<typename Tag>
static policy_type& safe_value(Tag&&)
{
return safe_value<Tag>();
}
template <typename Tag> static policy_type &safe_value(Tag &&) {
return safe_value<Tag>();
}
};
template<typename type_policy>
template<typename Tag>
typename per_tag<type_policy>::policy_type&
template <typename type_policy>
template <typename Tag>
typename per_tag<type_policy>::policy_type &
per_tag<type_policy>::per_tag_singleton<Tag>::value =
per_tag<type_policy>::per_tag_singleton<Tag>::safe_value();
// A class containing the range name, accumulated time and call count.
// This is used to track the total time spent in a scope, and the number of
// times the scope has been entered. This can be used to calculate the
......@@ -102,177 +87,156 @@ typename per_tag<type_policy>::policy_type&
// consistent, by the time all scopes in all threads are closed.
//
class range_stats {
const char* m_range_name {nullptr};
std::atomic<int64_t> m_accumulated_ns {0};
std::atomic<int64_t> m_call_count {0};
const char *m_range_name{nullptr};
std::atomic<int64_t> m_accumulated_ns{0};
std::atomic<int64_t> m_call_count{0};
public:
void set_range_name(const char* range_name) { m_range_name = range_name; }
const char* range_name() const { return m_range_name; }
public:
void set_range_name(const char *range_name) { m_range_name = range_name; }
const char *range_name() const { return m_range_name; }
std::chrono::nanoseconds accumulated_time() const
{
return std::chrono::nanoseconds{m_accumulated_ns.load()};
}
std::chrono::nanoseconds accumulated_time() const {
return std::chrono::nanoseconds{m_accumulated_ns.load()};
}
int64_t call_count() const { return m_call_count.load(); }
int64_t call_count() const { return m_call_count.load(); }
void add_range(std::chrono::nanoseconds time, size_t calls)
{
m_accumulated_ns += time.count();
m_call_count += calls;
}
void add_range(std::chrono::nanoseconds time, size_t calls) {
m_accumulated_ns += time.count();
m_call_count += calls;
}
// Default constructible, not movable or copyable.
range_stats() = default;
~range_stats() = default;
// Default constructible, not movable or copyable.
range_stats() = default;
~range_stats() = default;
range_stats(const range_stats&) = delete;
range_stats& operator=(const range_stats&) = delete;
range_stats(range_stats&&) = delete;
range_stats& operator=(range_stats&&) = delete;
range_stats(const range_stats &) = delete;
range_stats &operator=(const range_stats &) = delete;
range_stats(range_stats &&) = delete;
range_stats &operator=(range_stats &&) = delete;
};
template<typename Stream, typename Stats>
void print_range_stats(Stream& stream, const Stats& stats)
{
const std::chrono::nanoseconds accumulated_ns = stats.accumulated_time();
const double accumulated_seconds = accumulated_ns.count() * 1e-9;
template <typename Stream, typename Stats>
void print_range_stats(Stream &stream, const Stats &stats) {
const std::chrono::nanoseconds accumulated_ns = stats.accumulated_time();
const double accumulated_seconds = accumulated_ns.count() * 1e-9;
stream << stats.range_name() << ": " << accumulated_seconds << " s, "
<< (accumulated_seconds / stats.call_count()) << " s/call, "
<< stats.call_count() << " call(s)\n";
stream << stats.range_name() << ": " << accumulated_seconds << " s, "
<< (accumulated_seconds / stats.call_count()) << " s/call, "
<< stats.call_count() << " call(s)\n";
}
template<typename Stream, typename StatsPtrList>
void print_range_stats_list(Stream& stream, const StatsPtrList& stats_ptr_list)
{
const auto empty = [](const auto& stats) {
return stats->range_name() == nullptr;
};
const auto registry_empty = std::all_of(
std::cbegin(stats_ptr_list), std::cend(stats_ptr_list), empty);
if (!registry_empty) {
stream << "\n------------"
<< "\nRange Stats:"
<< "\n------------\n";
for (const auto& s : stats_ptr_list) {
if (s->range_name() == nullptr) continue;
print_range_stats(stream, *s);
}
stream << "\n------------\n";
template <typename Stream, typename StatsPtrList>
void print_range_stats_list(Stream &stream,
const StatsPtrList &stats_ptr_list) {
const auto empty = [](const auto &stats) {
return stats->range_name() == nullptr;
};
const auto registry_empty = std::all_of(std::cbegin(stats_ptr_list),
std::cend(stats_ptr_list), empty);
if (!registry_empty) {
stream << "\n------------"
<< "\nRange Stats:"
<< "\n------------\n";
for (const auto &s : stats_ptr_list) {
if (s->range_name() == nullptr)
continue;
print_range_stats(stream, *s);
}
stream << "\n------------\n";
}
}
class range_stats_print_on_exit_policy {
std::vector<range_stats*> m_range_stats_list{};
std::vector<range_stats *> m_range_stats_list{};
public:
using type = range_stats&;
public:
using type = range_stats &;
const std::vector<range_stats*>& range_stats_list() const
{
return m_range_stats_list;
}
template<typename Tag>
range_stats& make()
{
static range_stats m_tag_range_stats{};
m_range_stats_list.emplace_back(&m_tag_range_stats);
return m_tag_range_stats;
}
~range_stats_print_on_exit_policy()
{
print_range_stats_list(std::cout, m_range_stats_list);
}
};
const std::vector<range_stats *> &range_stats_list() const {
return m_range_stats_list;
}
template <typename Tag> range_stats &make() {
static range_stats m_tag_range_stats{};
m_range_stats_list.emplace_back(&m_tag_range_stats);
return m_tag_range_stats;
}
class per_tag_range_stats : public per_tag<range_stats_print_on_exit_policy> {
~range_stats_print_on_exit_policy() {
print_range_stats_list(std::cout, m_range_stats_list);
}
};
} // namespace detail
class per_tag_range_stats : public per_tag<range_stats_print_on_exit_policy> {};
} // namespace detail
template<
typename Tag,
size_t AggregateCount = 1,
typename Clock = std::chrono::high_resolution_clock>
template <typename Tag, size_t AggregateCount = 1,
typename Clock = std::chrono::high_resolution_clock>
class scoped_trace {
static_assert(
(AggregateCount & (AggregateCount - 1)) == 0,
"AggregateCount must be a power of 2");
static_assert((AggregateCount & (AggregateCount - 1)) == 0,
"AggregateCount must be a power of 2");
using time_point = typename Clock::time_point;
using time_point = typename Clock::time_point;
time_point m_t0 = Clock::now();
bool m_active = true;
time_point m_t0 = Clock::now();
bool m_active = true;
static detail::range_stats& stats()
{
return detail::per_tag_range_stats::value<Tag>();
}
static detail::range_stats &stats() {
return detail::per_tag_range_stats::value<Tag>();
}
public:
scoped_trace(const char* range_name)
{
assert(
stats().range_name() == nullptr
|| stats().range_name() == range_name);
public:
scoped_trace(const char *range_name) {
assert(stats().range_name() == nullptr ||
stats().range_name() == range_name);
stats().set_range_name(range_name);
}
stats().set_range_name(range_name);
}
scoped_trace(const scoped_trace&) = delete;
scoped_trace& operator=(const scoped_trace&) = delete;
scoped_trace(const scoped_trace &) = delete;
scoped_trace &operator=(const scoped_trace &) = delete;
// Move constructor marks other as inactive
scoped_trace(scoped_trace&& other) :
m_t0{std::move(other.m_t0)},
m_active{std::exchange(other.m_active, false)}
{
}
// Move constructor marks other as inactive
scoped_trace(scoped_trace &&other)
: m_t0{std::move(other.m_t0)}, m_active{std::exchange(other.m_active,
false)} {}
scoped_trace& operator=(scoped_trace&& other)
{
if (this != &other) {
m_t0 = std::move(other.m_t0);
m_active = std::exchange(other.m_active, false);
}
return *this;
scoped_trace &operator=(scoped_trace &&other) {
if (this != &other) {
m_t0 = std::move(other.m_t0);
m_active = std::exchange(other.m_active, false);
}
return *this;
}
~scoped_trace()
{
if (m_active) {
const auto now = Clock::now();
const auto delta = now - m_t0;
~scoped_trace() {
if (m_active) {
const auto now = Clock::now();
const auto delta = now - m_t0;
auto& s = stats();
s.add_range(delta, 1);
auto &s = stats();
s.add_range(delta, 1);
if (AggregateCount != 0 && s.call_count() % AggregateCount == 0) {
detail::print_range_stats(std::cout, s);
}
}
if (AggregateCount != 0 && s.call_count() % AggregateCount == 0) {
detail::print_range_stats(std::cout, s);
}
}
}
};
template<typename Tag, size_t AggregateCount = 0>
[[nodiscard]] auto make_scoped_trace(const char* range_name)
{
return scoped_trace<Tag, AggregateCount>(range_name);
template <typename Tag, size_t AggregateCount = 0>
[[nodiscard]] auto make_scoped_trace(const char *range_name) {
return scoped_trace<Tag, AggregateCount>(range_name);
}
template<size_t AggregateCount = 0, typename Tag = void>
[[nodiscard]] auto make_scoped_trace(const char* range_name, Tag&&)
{
return make_scoped_trace<Tag, AggregateCount>(range_name);
template <size_t AggregateCount = 0, typename Tag = void>
[[nodiscard]] auto make_scoped_trace(const char *range_name, Tag &&) {
return make_scoped_trace<Tag, AggregateCount>(range_name);
}
// Some machinery to make token concatenation work in macros
......@@ -281,21 +245,18 @@ template<size_t AggregateCount = 0, typename Tag = void>
// Create a scopes trace with a unique tag
#define PERFORATE_SCOPED_TRACE_V(RANGE_NAME) \
::perforate::make_scoped_trace(RANGE_NAME, [] {})
::perforate::make_scoped_trace(RANGE_NAME, [] {})
// Create a scoped trace with an "anonymous" variable name
// Note: this not be called outside a function scope!
#define PERFORATE_SCOPED_TRACE(RANGE_NAME) \
auto PERFORATE_DETAIL_COMBINE(_perforate_scoped_trace_v_, __LINE__) = \
PERFORATE_SCOPED_TRACE_V(RANGE_NAME)
auto PERFORATE_DETAIL_COMBINE(_perforate_scoped_trace_v_, __LINE__) = \
PERFORATE_SCOPED_TRACE_V(RANGE_NAME)
// End a scoped_trace early by "std::move"ing into this sink function
template<typename Tag, size_t AggregateCount>
void end_scoped_trace(scoped_trace<Tag, AggregateCount>)
{
}
template <typename Tag, size_t AggregateCount>
void end_scoped_trace(scoped_trace<Tag, AggregateCount>) {}
} // namespace perforate
} // namespace perforate
#endif // PERFORATE_TRACE_HPP
#endif // PERFORATE_TRACE_HPP
......@@ -10,27 +10,21 @@
#include <string>
#include <utility>
// Test detail::per_tag
class per_tag_test_helper {
public:
// Make a unique index per type
class index_policy {
public:
// Make a unique index per type
class index_policy {
public:
using type = int64_t;
type index;
template<typename Tag>
type make()
{
return ++index;
}
};
using type = int64_t;
class type_index : public perforate::detail::per_tag<index_policy> {
};
type index;
template <typename Tag> type make() { return ++index; }
};
class type_index : public perforate::detail::per_tag<index_policy> {};
};
using per_tag_index = per_tag_test_helper::type_index;
......@@ -44,286 +38,264 @@ auto index_3 = per_tag_index::safe_value([] {});
auto index_4 = per_tag_index::safe_value([] {});
// Generate indices inside a templated context
template<typename Tag>
struct templated_per_tag_index {
auto value() const
{
return per_tag_index::value([] {});
}
template <typename Tag> struct templated_per_tag_index {
auto value() const {
return per_tag_index::value([] {});
}
};
TEST_CASE(
"detail::per_tag should create unique instances for unique types",
"[per_tag]")
{
// Test an index was generated by per_tag_index::policy before calling
// this function.
const auto policy_index = per_tag_index::policy().index + 1;
const auto generated_pre_main = [policy_index](int64_t i) {
return i <= policy_index;
};
// Test an index hasn't been encountered by this function yet
std::set<int64_t> indices;
const auto index_is_unique = [&indices](int64_t i) {
const auto result = indices.insert(i);
return result.second == true;
};
{
INFO("Indices generated before main should be unique");
REQUIRE(generated_pre_main(index_1));
REQUIRE(index_is_unique(index_1));
REQUIRE(index_1 == per_tag_index::value<class index_1_tag>());
REQUIRE(generated_pre_main(index_2));
REQUIRE(index_is_unique(index_2));
REQUIRE(index_2 == per_tag_index::value<class index_2_tag>());
REQUIRE(generated_pre_main(index_3));
REQUIRE(index_is_unique(index_3));
REQUIRE(generated_pre_main(index_4));
REQUIRE(index_is_unique(index_4));
}
{
INFO("Indices generated after main should be unique");
auto index_5 = per_tag_index::value<class index_5_tag>();
REQUIRE(generated_pre_main(index_5));
REQUIRE(index_is_unique(index_5));
auto index_6 = per_tag_index::value<class index_6_tag>();
REQUIRE(generated_pre_main(index_6));
REQUIRE(index_is_unique(index_6));
auto index_7 = per_tag_index::value([] {});
REQUIRE(generated_pre_main(index_7));
REQUIRE(index_is_unique(index_7));
auto index_8 = per_tag_index::value([] {});
REQUIRE(generated_pre_main(index_8));
REQUIRE(index_is_unique(index_8));
}
{
INFO("Indices generated inside templated contexts should be unique");
auto index_9 = templated_per_tag_index<class index_9_tag>{}.value();
REQUIRE(generated_pre_main(index_9));
REQUIRE(index_is_unique(index_9));
auto index_10 = templated_per_tag_index<class index_10_tag>{}.value();
REQUIRE(generated_pre_main(index_10));
REQUIRE(index_is_unique(index_10));
}
{
INFO(
"Calling index twice for the same type should have the same index");
REQUIRE(
per_tag_index::value<class same_tag>()
== per_tag_index::value<class same_tag>());
}
TEST_CASE("detail::per_tag should create unique instances for unique types",
"[per_tag]") {
// Test an index was generated by per_tag_index::policy before calling
// this function.
const auto policy_index = per_tag_index::policy().index + 1;
const auto generated_pre_main = [policy_index](int64_t i) {
return i <= policy_index;
};
// Test an index hasn't been encountered by this function yet
std::set<int64_t> indices;
const auto index_is_unique = [&indices](int64_t i) {
const auto result = indices.insert(i);
return result.second == true;
};
{
INFO("Indices generated before main should be unique");
REQUIRE(generated_pre_main(index_1));