Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
performance
PERForate
Commits
0a1aacf6
Commit
0a1aacf6
authored
Apr 06, 2020
by
Pádraig Ó Conbhuí
Browse files
Add tools, run formatting
parent
52739195
Changes
6
Hide whitespace changes
Inline
Side-by-side
src/perforate/scoped_trace.bench.cpp
View file @
0a1aacf6
...
...
@@ -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
);
};
}
src/perforate/scoped_trace.hpp
View file @
0a1aacf6
...
...
@@ -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
------------"
<<
"
\n
Range 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
------------"
<<
"
\n
Range 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
src/perforate/scoped_trace.test.cpp
View file @
0a1aacf6
...
...
@@ -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
));