InternalBenchmark.tests.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. // Copyright Catch2 Authors
  2. // Distributed under the Boost Software License, Version 1.0.
  3. // (See accompanying file LICENSE_1_0.txt or copy at
  4. // https://www.boost.org/LICENSE_1_0.txt)
  5. // SPDX-License-Identifier: BSL-1.0
  6. // Adapted from donated nonius code.
  7. #if defined( __GNUC__ ) || defined( __clang__ )
  8. # pragma GCC diagnostic ignored "-Wfloat-equal"
  9. #endif
  10. #include <catch2/catch_test_macros.hpp>
  11. #include <catch2/catch_approx.hpp>
  12. #include <catch2/catch_config.hpp>
  13. #include <catch2/benchmark/catch_benchmark.hpp>
  14. #include <catch2/benchmark/catch_chronometer.hpp>
  15. #include <catch2/benchmark/detail/catch_analyse.hpp>
  16. #include <catch2/benchmark/detail/catch_benchmark_function.hpp>
  17. #include <catch2/benchmark/detail/catch_estimate_clock.hpp>
  18. namespace {
  19. struct manual_clock {
  20. public:
  21. using duration = std::chrono::nanoseconds;
  22. using time_point = std::chrono::time_point<manual_clock, duration>;
  23. using rep = duration::rep;
  24. using period = duration::period;
  25. enum { is_steady = true };
  26. static time_point now() {
  27. return time_point(duration(tick()));
  28. }
  29. static void advance(int ticks = 1) {
  30. tick() += ticks;
  31. }
  32. private:
  33. static rep& tick() {
  34. static rep the_tick = 0;
  35. return the_tick;
  36. }
  37. };
  38. struct counting_clock {
  39. public:
  40. using duration = std::chrono::nanoseconds;
  41. using time_point = std::chrono::time_point<counting_clock, duration>;
  42. using rep = duration::rep;
  43. using period = duration::period;
  44. enum { is_steady = true };
  45. static time_point now() {
  46. static rep ticks = 0;
  47. return time_point(duration(ticks += rate()));
  48. }
  49. static void set_rate(rep new_rate) { rate() = new_rate; }
  50. private:
  51. static rep& rate() {
  52. static rep the_rate = 1;
  53. return the_rate;
  54. }
  55. };
  56. struct TestChronometerModel : Catch::Benchmark::Detail::ChronometerConcept {
  57. int started = 0;
  58. int finished = 0;
  59. void start() override { ++started; }
  60. void finish() override { ++finished; }
  61. };
  62. } // namespace
  63. TEST_CASE("warmup", "[benchmark]") {
  64. auto rate = 1000;
  65. counting_clock::set_rate(rate);
  66. auto start = counting_clock::now();
  67. auto iterations = Catch::Benchmark::Detail::warmup<counting_clock>();
  68. auto end = counting_clock::now();
  69. REQUIRE((iterations * rate) > Catch::Benchmark::Detail::warmup_time.count());
  70. REQUIRE((end - start) > Catch::Benchmark::Detail::warmup_time);
  71. }
  72. TEST_CASE("resolution", "[benchmark]") {
  73. auto rate = 1000;
  74. counting_clock::set_rate(rate);
  75. size_t count = 10;
  76. auto res = Catch::Benchmark::Detail::resolution<counting_clock>(static_cast<int>(count));
  77. REQUIRE(res.size() == count);
  78. for (size_t i = 1; i < count; ++i) {
  79. REQUIRE(res[i] == rate);
  80. }
  81. }
  82. TEST_CASE("estimate_clock_resolution", "[benchmark]") {
  83. auto rate = 2'000;
  84. counting_clock::set_rate(rate);
  85. int iters = 160'000;
  86. auto res = Catch::Benchmark::Detail::estimate_clock_resolution<counting_clock>(iters);
  87. REQUIRE(res.mean.count() == rate);
  88. REQUIRE(res.outliers.total() == 0);
  89. }
  90. TEST_CASE("benchmark function call", "[benchmark]") {
  91. SECTION("without chronometer") {
  92. auto called = 0;
  93. auto model = TestChronometerModel{};
  94. auto meter = Catch::Benchmark::Chronometer{ model, 1 };
  95. auto fn = Catch::Benchmark::Detail::BenchmarkFunction{ [&] {
  96. CHECK(model.started == 1);
  97. CHECK(model.finished == 0);
  98. ++called;
  99. } };
  100. fn(meter);
  101. CHECK(model.started == 1);
  102. CHECK(model.finished == 1);
  103. CHECK(called == 1);
  104. }
  105. SECTION("with chronometer") {
  106. auto called = 0;
  107. auto model = TestChronometerModel{};
  108. auto meter = Catch::Benchmark::Chronometer{ model, 1 };
  109. auto fn = Catch::Benchmark::Detail::BenchmarkFunction{ [&](Catch::Benchmark::Chronometer) {
  110. CHECK(model.started == 0);
  111. CHECK(model.finished == 0);
  112. ++called;
  113. } };
  114. fn(meter);
  115. CHECK(model.started == 0);
  116. CHECK(model.finished == 0);
  117. CHECK(called == 1);
  118. }
  119. }
  120. TEST_CASE("uniform samples", "[benchmark]") {
  121. std::vector<double> samples(100);
  122. std::fill(samples.begin(), samples.end(), 23);
  123. using it = std::vector<double>::iterator;
  124. auto e = Catch::Benchmark::Detail::bootstrap(0.95, samples.begin(), samples.end(), samples, [](it a, it b) {
  125. auto sum = std::accumulate(a, b, 0.);
  126. return sum / (b - a);
  127. });
  128. CHECK(e.point == 23);
  129. CHECK(e.upper_bound == 23);
  130. CHECK(e.lower_bound == 23);
  131. CHECK(e.confidence_interval == 0.95);
  132. }
  133. TEST_CASE("normal_cdf", "[benchmark]") {
  134. using Catch::Benchmark::Detail::normal_cdf;
  135. using Catch::Approx;
  136. CHECK(normal_cdf(0.000000) == Approx(0.50000000000000000));
  137. CHECK(normal_cdf(1.000000) == Approx(0.84134474606854293));
  138. CHECK(normal_cdf(-1.000000) == Approx(0.15865525393145705));
  139. CHECK(normal_cdf(2.809729) == Approx(0.99752083845315409));
  140. CHECK(normal_cdf(-1.352570) == Approx(0.08809652095066035));
  141. }
  142. TEST_CASE("erfc_inv", "[benchmark]") {
  143. using Catch::Benchmark::Detail::erfc_inv;
  144. using Catch::Approx;
  145. CHECK(erfc_inv(1.103560) == Approx(-0.09203687623843015));
  146. CHECK(erfc_inv(1.067400) == Approx(-0.05980291115763361));
  147. CHECK(erfc_inv(0.050000) == Approx(1.38590382434967796));
  148. }
  149. TEST_CASE("normal_quantile", "[benchmark]") {
  150. using Catch::Benchmark::Detail::normal_quantile;
  151. using Catch::Approx;
  152. CHECK(normal_quantile(0.551780) == Approx(0.13015979861484198));
  153. CHECK(normal_quantile(0.533700) == Approx(0.08457408802851875));
  154. CHECK(normal_quantile(0.025000) == Approx(-1.95996398454005449));
  155. }
  156. TEST_CASE("mean", "[benchmark]") {
  157. std::vector<double> x{ 10., 20., 14., 16., 30., 24. };
  158. auto m = Catch::Benchmark::Detail::mean(x.begin(), x.end());
  159. REQUIRE(m == 19.);
  160. }
  161. TEST_CASE("weighted_average_quantile", "[benchmark]") {
  162. std::vector<double> x{ 10., 20., 14., 16., 30., 24. };
  163. auto q1 = Catch::Benchmark::Detail::weighted_average_quantile(1, 4, x.begin(), x.end());
  164. auto med = Catch::Benchmark::Detail::weighted_average_quantile(1, 2, x.begin(), x.end());
  165. auto q3 = Catch::Benchmark::Detail::weighted_average_quantile(3, 4, x.begin(), x.end());
  166. REQUIRE(q1 == 14.5);
  167. REQUIRE(med == 18.);
  168. REQUIRE(q3 == 23.);
  169. }
  170. TEST_CASE("classify_outliers", "[benchmark]") {
  171. auto require_outliers = [](Catch::Benchmark::OutlierClassification o, int los, int lom, int him, int his) {
  172. REQUIRE(o.low_severe == los);
  173. REQUIRE(o.low_mild == lom);
  174. REQUIRE(o.high_mild == him);
  175. REQUIRE(o.high_severe == his);
  176. REQUIRE(o.total() == los + lom + him + his);
  177. };
  178. SECTION("none") {
  179. std::vector<double> x{ 10., 20., 14., 16., 30., 24. };
  180. auto o = Catch::Benchmark::Detail::classify_outliers(x.begin(), x.end());
  181. REQUIRE(o.samples_seen == static_cast<int>(x.size()));
  182. require_outliers(o, 0, 0, 0, 0);
  183. }
  184. SECTION("low severe") {
  185. std::vector<double> x{ -12., 20., 14., 16., 30., 24. };
  186. auto o = Catch::Benchmark::Detail::classify_outliers(x.begin(), x.end());
  187. REQUIRE(o.samples_seen == static_cast<int>(x.size()));
  188. require_outliers(o, 1, 0, 0, 0);
  189. }
  190. SECTION("low mild") {
  191. std::vector<double> x{ 1., 20., 14., 16., 30., 24. };
  192. auto o = Catch::Benchmark::Detail::classify_outliers(x.begin(), x.end());
  193. REQUIRE(o.samples_seen == static_cast<int>(x.size()));
  194. require_outliers(o, 0, 1, 0, 0);
  195. }
  196. SECTION("high mild") {
  197. std::vector<double> x{ 10., 20., 14., 16., 36., 24. };
  198. auto o = Catch::Benchmark::Detail::classify_outliers(x.begin(), x.end());
  199. REQUIRE(o.samples_seen == static_cast<int>(x.size()));
  200. require_outliers(o, 0, 0, 1, 0);
  201. }
  202. SECTION("high severe") {
  203. std::vector<double> x{ 10., 20., 14., 16., 49., 24. };
  204. auto o = Catch::Benchmark::Detail::classify_outliers(x.begin(), x.end());
  205. REQUIRE(o.samples_seen == static_cast<int>(x.size()));
  206. require_outliers(o, 0, 0, 0, 1);
  207. }
  208. SECTION("mixed") {
  209. std::vector<double> x{ -20., 20., 14., 16., 39., 24. };
  210. auto o = Catch::Benchmark::Detail::classify_outliers(x.begin(), x.end());
  211. REQUIRE(o.samples_seen == static_cast<int>(x.size()));
  212. require_outliers(o, 1, 0, 1, 0);
  213. }
  214. }
  215. TEST_CASE("analyse", "[approvals][benchmark]") {
  216. Catch::ConfigData data{};
  217. data.benchmarkConfidenceInterval = 0.95;
  218. data.benchmarkNoAnalysis = false;
  219. data.benchmarkResamples = 1000;
  220. data.benchmarkSamples = 99;
  221. Catch::Config config{data};
  222. using Duration = Catch::Benchmark::FloatDuration<Catch::Benchmark::default_clock>;
  223. Catch::Benchmark::Environment<Duration> env;
  224. std::vector<Duration> samples(99);
  225. for (size_t i = 0; i < samples.size(); ++i) {
  226. samples[i] = Duration(23 + (i % 3 - 1));
  227. }
  228. auto analysis = Catch::Benchmark::Detail::analyse(config, env, samples.begin(), samples.end());
  229. CHECK( analysis.mean.point.count() == 23 );
  230. CHECK( analysis.mean.lower_bound.count() < 23 );
  231. CHECK(analysis.mean.lower_bound.count() > 22);
  232. CHECK(analysis.mean.upper_bound.count() > 23);
  233. CHECK(analysis.mean.upper_bound.count() < 24);
  234. CHECK(analysis.standard_deviation.point.count() > 0.5);
  235. CHECK(analysis.standard_deviation.point.count() < 1);
  236. CHECK(analysis.standard_deviation.lower_bound.count() > 0.5);
  237. CHECK(analysis.standard_deviation.lower_bound.count() < 1);
  238. CHECK(analysis.standard_deviation.upper_bound.count() > 0.5);
  239. CHECK(analysis.standard_deviation.upper_bound.count() < 1);
  240. CHECK(analysis.outliers.total() == 0);
  241. CHECK(analysis.outliers.low_mild == 0);
  242. CHECK(analysis.outliers.low_severe == 0);
  243. CHECK(analysis.outliers.high_mild == 0);
  244. CHECK(analysis.outliers.high_severe == 0);
  245. CHECK(analysis.outliers.samples_seen == static_cast<int>(samples.size()));
  246. CHECK(analysis.outlier_variance < 0.5);
  247. CHECK(analysis.outlier_variance > 0);
  248. }
  249. TEST_CASE("analyse no analysis", "[benchmark]") {
  250. Catch::ConfigData data{};
  251. data.benchmarkConfidenceInterval = 0.95;
  252. data.benchmarkNoAnalysis = true;
  253. data.benchmarkResamples = 1000;
  254. data.benchmarkSamples = 99;
  255. Catch::Config config{ data };
  256. using Duration = Catch::Benchmark::FloatDuration<Catch::Benchmark::default_clock>;
  257. Catch::Benchmark::Environment<Duration> env;
  258. std::vector<Duration> samples(99);
  259. for (size_t i = 0; i < samples.size(); ++i) {
  260. samples[i] = Duration(23 + (i % 3 - 1));
  261. }
  262. auto analysis = Catch::Benchmark::Detail::analyse(config, env, samples.begin(), samples.end());
  263. CHECK(analysis.mean.point.count() == 23);
  264. CHECK(analysis.mean.lower_bound.count() == 23);
  265. CHECK(analysis.mean.upper_bound.count() == 23);
  266. CHECK(analysis.standard_deviation.point.count() == 0);
  267. CHECK(analysis.standard_deviation.lower_bound.count() == 0);
  268. CHECK(analysis.standard_deviation.upper_bound.count() == 0);
  269. CHECK(analysis.outliers.total() == 0);
  270. CHECK(analysis.outliers.low_mild == 0);
  271. CHECK(analysis.outliers.low_severe == 0);
  272. CHECK(analysis.outliers.high_mild == 0);
  273. CHECK(analysis.outliers.high_severe == 0);
  274. CHECK(analysis.outliers.samples_seen == 0);
  275. CHECK(analysis.outlier_variance == 0);
  276. }
  277. TEST_CASE("run_for_at_least, int", "[benchmark]") {
  278. manual_clock::duration time(100);
  279. int old_x = 1;
  280. auto Timing = Catch::Benchmark::Detail::run_for_at_least<manual_clock>(time, 1, [&old_x](int x) -> int {
  281. CHECK(x >= old_x);
  282. manual_clock::advance(x);
  283. old_x = x;
  284. return x + 17;
  285. });
  286. REQUIRE(Timing.elapsed >= time);
  287. REQUIRE(Timing.result == Timing.iterations + 17);
  288. REQUIRE(Timing.iterations >= time.count());
  289. }
  290. TEST_CASE("run_for_at_least, chronometer", "[benchmark]") {
  291. manual_clock::duration time(100);
  292. int old_runs = 1;
  293. auto Timing = Catch::Benchmark::Detail::run_for_at_least<manual_clock>(time, 1, [&old_runs](Catch::Benchmark::Chronometer meter) -> int {
  294. CHECK(meter.runs() >= old_runs);
  295. manual_clock::advance(100);
  296. meter.measure([] {
  297. manual_clock::advance(1);
  298. });
  299. old_runs = meter.runs();
  300. return meter.runs() + 17;
  301. });
  302. REQUIRE(Timing.elapsed >= time);
  303. REQUIRE(Timing.result == Timing.iterations + 17);
  304. REQUIRE(Timing.iterations >= time.count());
  305. }
  306. TEST_CASE("measure", "[benchmark]") {
  307. auto r = Catch::Benchmark::Detail::measure<manual_clock>([](int x) -> int {
  308. CHECK(x == 17);
  309. manual_clock::advance(42);
  310. return 23;
  311. }, 17);
  312. auto s = Catch::Benchmark::Detail::measure<manual_clock>([](int x) -> int {
  313. CHECK(x == 23);
  314. manual_clock::advance(69);
  315. return 17;
  316. }, 23);
  317. CHECK(r.elapsed.count() == 42);
  318. CHECK(r.result == 23);
  319. CHECK(r.iterations == 1);
  320. CHECK(s.elapsed.count() == 69);
  321. CHECK(s.result == 17);
  322. CHECK(s.iterations == 1);
  323. }
  324. TEST_CASE("run benchmark", "[benchmark][approvals]") {
  325. counting_clock::set_rate(1000);
  326. auto start = counting_clock::now();
  327. Catch::Benchmark::Benchmark bench{ "Test Benchmark", [](Catch::Benchmark::Chronometer meter) {
  328. counting_clock::set_rate(100000);
  329. meter.measure([] { return counting_clock::now(); });
  330. } };
  331. bench.run<counting_clock>();
  332. auto end = counting_clock::now();
  333. CHECK((end - start).count() == 2867251000);
  334. }
  335. TEST_CASE("Failing benchmarks", "[!benchmark][.approvals]") {
  336. SECTION("empty", "Benchmark that has been optimized away (because it is empty)") {
  337. BENCHMARK("Empty benchmark") {};
  338. }
  339. SECTION("throw", "Benchmark that throws an exception") {
  340. BENCHMARK("Throwing benchmark") {
  341. throw "just a plain literal, bleh";
  342. };
  343. }
  344. SECTION("assert", "Benchmark that asserts inside") {
  345. BENCHMARK("Asserting benchmark") {
  346. REQUIRE(1 == 2);
  347. };
  348. }
  349. SECTION("fail", "Benchmark that fails inside") {
  350. BENCHMARK("FAIL'd benchmark") {
  351. FAIL("This benchmark only fails, nothing else");
  352. };
  353. }
  354. }
  355. TEST_CASE( "Failing benchmark respects should-fail",
  356. "[!shouldfail][!benchmark][.approvals]" ) {
  357. BENCHMARK( "Asserting benchmark" ) { REQUIRE( 1 == 2 ); };
  358. }