// // 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 #include #if __cplusplus >= 202002L #elif defined(__has_include) && __has_include() #include using date::operator<<; #else namespace std::chrono { template ostream &operator<<(ostream &os, duration 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 struct DurationImpl { using type = std::chrono::nanoseconds; }; template struct DurationImpl> { using type = std::chrono::duration; }; template struct PerformanceTestDuration { Clock::duration average() const { return (ticks.back() - ticks.front()) / size(); } operator Duration() const { return std::chrono::duration_cast(ticks.back() - ticks.front()); } size_t size() const { return ticks.size() - 1; } Duration operator()(size_t i = 0) const { return std::chrono::duration_cast(ticks.at(i + 1) - ticks.at(i)); } void lap() { ticks.push_back(Clock::now()); } std::vector ticks{}; }; template void PrintTo(PerformanceTestDuration const &ptd, std::ostream *os) { (*os) << "duration: " << static_cast(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 struct first_type { using type = Clock::duration; }; template