performance_test.h 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. //
  2. // performance_test.h
  3. //
  4. // Created by Sam Jaffe on 11/4/23.
  5. // Copyright © 2020 Sam Jaffe. All rights reserved.
  6. //
  7. #pragma once
  8. #define CONCAT2(A, B) A##B
  9. #define CONCAT(A, B) CONCAT2(A, B)
  10. #include <chrono>
  11. #include <testing/xcode_gtest_helper.h>
  12. #if __cplusplus >= 202002L
  13. #elif defined(__has_include) && __has_include(<date/date.h>)
  14. #include <date/date.h>
  15. using date::operator<<;
  16. #else
  17. namespace std::chrono {
  18. template <typename Rep, typename Period>
  19. ostream &operator<<(ostream &os, duration<Rep, Period> const & dur) {
  20. os << dur.count();
  21. if (Period::den == 1) {
  22. switch (Period::num) {
  23. case 1: return os << 's';
  24. case 60: return os << 'm';
  25. case 3600: return os << 'h';
  26. case 86400: return os << 'd';
  27. }
  28. } else if (Period::num == 1) {
  29. switch (Period::den) {
  30. case 1000: return os << "ms";
  31. case 1000'000: return os << "\u00B5s";
  32. case 1000'000'000: return os << "ns";
  33. }
  34. }
  35. return os << Period::num << '/' << Period::den << 's';
  36. }
  37. }
  38. #endif
  39. #define PERF_TEST_CLASS(name) CONCAT(PerfTest, name)
  40. /**
  41. * Example code using TEST_PERF:
  42. *
  43. * TEST_PERF(MyClassTest, Initialization, 50ms) {
  44. * ... setup input variables ...
  45. * MyClass object(...);
  46. * (void) object;
  47. * }
  48. *
  49. * void ComplexTest::SetUp() {
  50. * ... setup input variables as above to keep out of perf test ...
  51. * object = MyClass(...);
  52. * }
  53. *
  54. * TEST_PERF_F(ComplexTest, ExpensiveFunction, 100ms) {
  55. * this->object.ExpensiveFunction();
  56. * }
  57. */
  58. #define TEST_PERF(test_suite_name, test_name, limit) \
  59. PERFORMANCE_FIXTURE_IMPL(test_suite_name, test_name, testing::Test, limit)
  60. #define TEST_PERF_F(test_fixture, test_name, limit) \
  61. PERFORMANCE_FIXTURE_IMPL(test_suite_name, test_name, test_suite_name, limit)
  62. #define PERFORMANCE_TEST_CLASS(suite, case) CONCAT(suite, CONCAT(_, case))
  63. #define PERFORMANCE_FIXTURE_IMPL(suite, case, parent, limit) \
  64. class PERFORMANCE_TEST_CLASS(suite, case) : public parent { \
  65. protected: \
  66. void TestImpl(); \
  67. }; \
  68. TEST_F(PERFORMANCE_TEST_CLASS(suite, case), Performance) { \
  69. EXPECT_PERFORMANCE(TestImpl(), limit); \
  70. } \
  71. void PERFORMANCE_TEST_CLASS(suite, case)::TestImpl()
  72. /**
  73. * Example code using EXPECT_PERFORMANCE:
  74. * using std::literals::chrono_literals::operator""ms;
  75. *
  76. * TEST(MyClassPerfTest, Initialization) {
  77. * EXPECT_PERFORMANCE(MyClass(...), testing::Le(50ms));
  78. * }
  79. *
  80. * TEST(MyClassPerfTest, ExpensiveFunction) {
  81. * MyClass object(...);
  82. * EXPECT_PERFORMANCE(object.ExpensiveFunction(), testing::Le(100ms));
  83. * }
  84. */
  85. #define EXPECT_PERFORMANCE(expression, matcher) \
  86. EXPECT_PERFORMANCE_IMPL(expression, matcher, 1UL)
  87. namespace testing::perf {
  88. using Clock = std::chrono::high_resolution_clock;
  89. template <typename> struct DurationImpl {
  90. using type = std::chrono::nanoseconds;
  91. };
  92. template <typename Rep, typename Ratio>
  93. struct DurationImpl<std::chrono::duration<Rep, Ratio>> {
  94. using type = std::chrono::duration<Rep, Ratio>;
  95. };
  96. template <typename Duration>
  97. struct PerformanceTestDuration {
  98. Clock::duration average() const { return (ticks.back() - ticks.front()) / size(); }
  99. operator Duration() const {
  100. return std::chrono::duration_cast<Duration>(ticks.back() - ticks.front());
  101. }
  102. size_t size() const { return ticks.size() - 1; }
  103. Duration operator()(size_t i = 0) const {
  104. return std::chrono::duration_cast<Duration>(ticks.at(i + 1) - ticks.at(i));
  105. }
  106. void lap() { ticks.push_back(Clock::now()); }
  107. std::vector<Clock::time_point> ticks{};
  108. };
  109. template <typename Duration>
  110. void PrintTo(PerformanceTestDuration<Duration> const &ptd, std::ostream *os) {
  111. (*os) << "duration: " << static_cast<Duration>(ptd);
  112. if (size_t const n = ptd.size(); n > 1) {
  113. [[maybe_unused]] int const digits = n == 1 ? 1 : std::log10(n - 1) + 1;
  114. double X = 0, X2 = 0;
  115. for (size_t i = 0; i < n; ++i) {
  116. auto d = ptd(i);
  117. X += d.count();
  118. X2 += std::pow(d.count(), 2);
  119. # if defined(PERFORMANCE_TEST_PRINT_LAPS)
  120. (*os) << "\nlap " << std::setw(digits) << i << ": " << d;
  121. # endif
  122. }
  123. long long E = X / n;
  124. long long S = std::sqrt(X2 / n - std::pow(X / n, 2));
  125. (*os) << "\naverage: " << Duration(E);
  126. (*os) << "\nstd.dev: " << Duration(S);
  127. }
  128. }
  129. template <typename> struct first_type {
  130. using type = Clock::duration;
  131. };
  132. template <template <typename...> class Template, typename Arg0, typename Arg1, typename... Args>
  133. struct first_type<Template<Arg0, Arg1, Args...>> {
  134. using type = std::decay_t<Arg1>;
  135. };
  136. template <typename T> using first_type_t = typename first_type<T>::type;
  137. MATCHER_P(FasterThan, limit, "is < " + testing::PrintToString(limit)) {
  138. return arg.average() < limit;
  139. }
  140. MATCHER_P2(BetweenTimes, low, high,
  141. "is between " + testing::PrintToString(low) + " and " +
  142. testing::PrintToString(high)) {
  143. return arg.average() < high && arg.average() > low;
  144. }
  145. MATCHER_P2(OnAverage, n, m,
  146. (n == 1 ? "" : "on average ") + DescribeMatcher<arg_type>(m)) {
  147. return testing::Matcher<arg_type>(m).MatchAndExplain(arg, result_listener);
  148. }
  149. }
  150. #define EXPECT_PERFORMANCE_IMPL(expression, matcher, n_reps) \
  151. do { \
  152. using namespace testing::perf; \
  153. using namespace std::literals::chrono_literals; \
  154. \
  155. using duration_t = first_type_t<decltype(matcher)>; \
  156. PerformanceTestDuration<duration_t> duration; \
  157. \
  158. for (size_t i = 0; i < n_reps; ++i) { \
  159. duration.lap(); \
  160. (void) expression; \
  161. } \
  162. duration.lap(); \
  163. \
  164. EXPECT_THAT(duration, OnAverage(n_reps, matcher)); \
  165. } while (false)
  166. #define TEST_PERF_IMPL(test_suite_name, test_name, parent_class, limit) \
  167. class PERF_TEST_CLASS(test_suite_name) : public parent_class { \
  168. protected: \
  169. void TestImpl(); \
  170. }; \
  171. TEST_F(PERF_TEST_CLASS(test_suite_name), test_name) { \
  172. EXPECT_PERFORMANCE(TestImpl(), limit); \
  173. } \
  174. void PERF_TEST_CLASS(test_suite_name)::TestImpl()