| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- //
- // performance_test.h
- //
- // Created by Sam Jaffe on 11/4/23.
- // Copyright © 2020 Sam Jaffe. All rights reserved.
- //
- #pragma once
- #define CONCAT2(A, B) A##B
- #define CONCAT(A, B) CONCAT2(A, B)
- #include <chrono>
- #include <testing/xcode_gtest_helper.h>
- #if __cplusplus >= 202002L
- #elif defined(__has_include) && __has_include(<date/date.h>)
- #include <date/date.h>
- using date::operator<<;
- #else
- namespace std::chrono {
- template <typename Rep, typename Period>
- ostream &operator<<(ostream &os, duration<Rep, Period> const & dur) {
- os << dur.count();
- if (Period::den == 1) {
- switch (Period::num) {
- case 1: return os << 's';
- case 60: return os << 'm';
- case 3600: return os << 'h';
- case 86400: return os << 'd';
- }
- } else if (Period::num == 1) {
- switch (Period::den) {
- case 1000: return os << "ms";
- case 1000'000: return os << "\u00B5s";
- case 1000'000'000: return os << "ns";
- }
- }
- return os << Period::num << '/' << Period::den << 's';
- }
- }
- #endif
- #define PERF_TEST_CLASS(name) CONCAT(PerfTest, name)
- /**
- * Example code using TEST_PERF:
- *
- * TEST_PERF(MyClassTest, Initialization, 50ms) {
- * ... setup input variables ...
- * MyClass object(...);
- * (void) object;
- * }
- *
- * void ComplexTest::SetUp() {
- * ... setup input variables as above to keep out of perf test ...
- * object = MyClass(...);
- * }
- *
- * TEST_PERF_F(ComplexTest, ExpensiveFunction, 100ms) {
- * this->object.ExpensiveFunction();
- * }
- */
- #define TEST_PERF(test_suite_name, test_name, limit) \
- PERFORMANCE_FIXTURE_IMPL(test_suite_name, test_name, testing::Test, limit)
- #define TEST_PERF_F(test_fixture, test_name, limit) \
- PERFORMANCE_FIXTURE_IMPL(test_suite_name, test_name, test_suite_name, limit)
- #define PERFORMANCE_TEST_CLASS(suite, case) CONCAT(suite, CONCAT(_, case))
- #define PERFORMANCE_FIXTURE_IMPL(suite, case, parent, limit) \
- class PERFORMANCE_TEST_CLASS(suite, case) : public parent { \
- protected: \
- void TestImpl(); \
- }; \
- TEST_F(PERFORMANCE_TEST_CLASS(suite, case), Performance) { \
- EXPECT_PERFORMANCE(TestImpl(), limit); \
- } \
- void PERFORMANCE_TEST_CLASS(suite, case)::TestImpl()
- /**
- * Example code using EXPECT_PERFORMANCE:
- * using std::literals::chrono_literals::operator""ms;
- *
- * TEST(MyClassPerfTest, Initialization) {
- * EXPECT_PERFORMANCE(MyClass(...), testing::Le(50ms));
- * }
- *
- * TEST(MyClassPerfTest, ExpensiveFunction) {
- * MyClass object(...);
- * EXPECT_PERFORMANCE(object.ExpensiveFunction(), testing::Le(100ms));
- * }
- */
- #define EXPECT_PERFORMANCE(expression, matcher) \
- EXPECT_PERFORMANCE_IMPL(expression, matcher, 1UL)
- namespace testing::perf {
- using Clock = std::chrono::high_resolution_clock;
- template <typename> struct DurationImpl {
- using type = std::chrono::nanoseconds;
- };
- template <typename Rep, typename Ratio>
- struct DurationImpl<std::chrono::duration<Rep, Ratio>> {
- using type = std::chrono::duration<Rep, Ratio>;
- };
- template <typename Duration>
- struct PerformanceTestDuration {
- Clock::duration average() const { return (ticks.back() - ticks.front()) / size(); }
- operator Duration() const {
- return std::chrono::duration_cast<Duration>(ticks.back() - ticks.front());
- }
- size_t size() const { return ticks.size() - 1; }
- Duration operator()(size_t i = 0) const {
- return std::chrono::duration_cast<Duration>(ticks.at(i + 1) - ticks.at(i));
- }
- void lap() { ticks.push_back(Clock::now()); }
-
- std::vector<Clock::time_point> ticks{};
- };
- template <typename Duration>
- void PrintTo(PerformanceTestDuration<Duration> const &ptd, std::ostream *os) {
- (*os) << "duration: " << static_cast<Duration>(ptd);
- if (size_t const n = ptd.size(); n > 1) {
- [[maybe_unused]] int const digits = n == 1 ? 1 : std::log10(n - 1) + 1;
- double X = 0, X2 = 0;
-
- for (size_t i = 0; i < n; ++i) {
- auto d = ptd(i);
- X += d.count();
- X2 += std::pow(d.count(), 2);
- # if defined(PERFORMANCE_TEST_PRINT_LAPS)
- (*os) << "\nlap " << std::setw(digits) << i << ": " << d;
- # endif
- }
-
- long long E = X / n;
- long long S = std::sqrt(X2 / n - std::pow(X / n, 2));
- (*os) << "\naverage: " << Duration(E);
- (*os) << "\nstd.dev: " << Duration(S);
- }
- }
- template <typename> struct first_type {
- using type = Clock::duration;
- };
- template <template <typename...> class Template, typename Arg0, typename Arg1, typename... Args>
- struct first_type<Template<Arg0, Arg1, Args...>> {
- using type = std::decay_t<Arg1>;
- };
- template <typename T> using first_type_t = typename first_type<T>::type;
- MATCHER_P(FasterThan, limit, "is < " + testing::PrintToString(limit)) {
- return arg.average() < limit;
- }
- MATCHER_P2(BetweenTimes, low, high,
- "is between " + testing::PrintToString(low) + " and " +
- testing::PrintToString(high)) {
- return arg.average() < high && arg.average() > low;
- }
- MATCHER_P2(OnAverage, n, m,
- (n == 1 ? "" : "on average ") + DescribeMatcher<arg_type>(m)) {
- return testing::Matcher<arg_type>(m).MatchAndExplain(arg, result_listener);
- }
- }
- #define EXPECT_PERFORMANCE_IMPL(expression, matcher, n_reps) \
- do { \
- using namespace testing::perf; \
- using namespace std::literals::chrono_literals; \
- \
- using duration_t = first_type_t<decltype(matcher)>; \
- PerformanceTestDuration<duration_t> duration; \
- \
- for (size_t i = 0; i < n_reps; ++i) { \
- duration.lap(); \
- (void) expression; \
- } \
- duration.lap(); \
- \
- EXPECT_THAT(duration, OnAverage(n_reps, matcher)); \
- } while (false)
- #define TEST_PERF_IMPL(test_suite_name, test_name, parent_class, limit) \
- class PERF_TEST_CLASS(test_suite_name) : public parent_class { \
- protected: \
- void TestImpl(); \
- }; \
- TEST_F(PERF_TEST_CLASS(test_suite_name), test_name) { \
- EXPECT_PERFORMANCE(TestImpl(), limit); \
- } \
- void PERF_TEST_CLASS(test_suite_name)::TestImpl()
|