23namespace sequoia::testing
25 template<std::invocable Task>
27 std::chrono::duration<double> profile(Task task)
32 return t.time_elapsed();
77 template<test_mode Mode, std::invocable F, std::invocable S>
78 bool check_relative_performance(std::string_view description,
test_logger<Mode>& logger, F fast, S slow,
const double minSpeedUp,
const double maxSpeedUp,
const std::size_t trials,
const double num_sds,
const std::size_t maxAttempts)
80 if((minSpeedUp <= 1) || (maxSpeedUp <= 1))
81 throw std::logic_error{
"Relative performance test requires speed-up factors > 1"};
83 if(minSpeedUp > maxSpeedUp)
84 throw std::logic_error{
"maxSpeedUp must be >= minSpeedUp"};
87 throw std::logic_error{
"Number of standard deviations is required to be > 1"};
90 throw std::logic_error{
"Number of attempts is required to be > 0"};
93 throw std::logic_error{
"Number of trials is required to be > 4"};
95 using namespace std::chrono;
96 using namespace maths;
98 std::string summary{};
99 std::size_t remainingAttempts{maxAttempts};
103 [](
auto task, std::vector<double>& timings){
104 timings.push_back(profile(task).count());
108 while(remainingAttempts > 0)
110 const auto adjustedTrials{trials*(maxAttempts - remainingAttempts + 1)};
112 std::vector<double> fastData, slowData;
113 fastData.reserve(adjustedTrials);
114 slowData.reserve(adjustedTrials);
116 std::random_device generator;
117 for(std::size_t i{}; i < adjustedTrials; ++i)
119 std::uniform_real_distribution<double> distribution{0.0, 1.0};
120 const bool fastFirst{(distribution(generator) < 0.5)};
124 timer(fast, fastData);
125 timer(slow, slowData);
129 timer(slow, slowData);
130 timer(fast, fastData);
135 [](
auto first,
auto last) {
136 const auto data{sample_standard_deviation(first, last)};
137 return std::make_pair(data.first.value(), data.second.value());
141 std::ranges::sort(fastData);
142 std::ranges::sort(slowData);
144 const auto [sig_f, m_f]{compute_stats(fastData.cbegin()+1, fastData.cend()-1)};
145 const auto [sig_s, m_s]{compute_stats(slowData.cbegin()+1, slowData.cend()-1)};
147 if(m_f + sig_f < m_s - sig_s)
151 passed = (minSpeedUp * m_f <= (m_s + num_sds * sig_s))
152 && (maxSpeedUp * m_f >= (m_s - num_sds * sig_s));
156 passed = (m_s / maxSpeedUp <= (m_f + num_sds * sig_f))
157 && (m_s / minSpeedUp >= (m_f - num_sds * sig_f));
166 [num_sds](std::string_view prefix,
const auto mean,
const auto sig){
168 std::ostringstream message{};
169 message << mean <<
"s" <<
" +- " << num_sds <<
" * " << sig <<
"s";
171 return std::string{prefix}.append(
" Task duration: ").append(message.str());
176 [m_f{m_f},m_s{m_s},minSpeedUp,maxSpeedUp](){
177 std::ostringstream message{};
178 message <<
" [" << m_s / m_f <<
"; (" << minSpeedUp <<
", " << maxSpeedUp <<
")]";
180 return message.str();
184 summary = append_lines(stats(
"Fast", m_f, sig_f), stats(
"Slow", m_s, sig_s)).append(summarizer());
194 sentinel<Mode> sentry{logger, append_lines(description, summary)};
195 sentry.log_performance_check();
199 sentry.log_performance_failure(
"");
205 template<
class T,
class Period>
207 std::chrono::duration<T, Period> calibrate(std::chrono::duration<T, Period> target)
209 using namespace std::chrono;
211 std::array<double, 7> timings{};
212 for (
auto& t : timings)
214 t = profile([target]() { std::this_thread::sleep_for(target); }).count();
217 std::ranges::sort(timings);
218 const auto [sig_f, m_f] {maths::sample_standard_deviation(timings.cbegin() + 1, timings.cend() - 1)};
221 if ((m_f.value() - sig_f.value()) > duration_cast<duration<double>>(target).count())
223 constexpr auto inverse{Period::den / Period::num};
224 return std::chrono::duration<T, Period>{
static_cast<T
>(std::ceil(inverse* (m_f.value() + 5* sig_f.value())))};
234 template<test_mode Mode>
238 constexpr static test_mode mode{Mode};
242 template<
class Self, std::invocable F, std::invocable S>
243 bool check_relative_performance(
this Self& self,
const reporter& description, F fast, S slow,
const double minSpeedUp,
const double maxSpeedUp,
const std::size_t trials=5,
const double num_sds=4)
245 return testing::check_relative_performance(self.report(description), self.m_Logger, fast, slow, minSpeedUp, maxSpeedUp, trials, num_sds, 3);
255 std::string_view postprocess(std::string_view testOutput, std::string_view referenceOutput);
259 template<test_mode Mode>
264 using duration =
typename base_type::duration;
266 using base_type::base_type;
282 template<concrete_test T>
283 requires std::is_base_of_v<basic_performance_test<T::mode>, T>
Contains utilities for automatically editing certain files as part of the test creation process.
Utilities for checking regular semantics.
Tools for statistical analysis.
class template from which all concrete tests should derive.
Definition: FreeTestCore.hpp:144
Summaries data generated by the logger, for the purposes of reporting.
Definition: TestLogger.hpp:299
Definition: Output.hpp:186
Definition: TestLogger.hpp:277
Definition: TestLogger.hpp:183
Definition: FreeTestCore.hpp:31
Definition: FreeTestCore.hpp:241