hayai_benchmarker.hpp
Go to the documentation of this file.
00001 #ifndef __HAYAI_BENCHMARKER
00002 #define __HAYAI_BENCHMARKER
00003 #include <algorithm>
00004 #include <vector>
00005 #include <limits>
00006 #include <iomanip>
00007 #include <string>
00008 #include <cstring>
00009 
00010 #include "hayai/hayai_test_factory.hpp"
00011 #include "hayai/hayai_test_descriptor.hpp"
00012 #include "hayai/hayai_test_result.hpp"
00013 #include "hayai/hayai_console_outputter.hpp"
00014 
00015 
00016 namespace hayai
00017 {
00019     class Benchmarker
00020     {
00021     public:
00023 
00026         static Benchmarker& Instance()
00027         {
00028             static Benchmarker singleton;
00029             return singleton;
00030         }
00031 
00032 
00034 
00042         static TestDescriptor* RegisterTest(
00043             const char* fixtureName,
00044             const char* testName,
00045             std::size_t runs,
00046             std::size_t iterations,
00047             TestFactory* testFactory,
00048             TestParametersDescriptor parameters
00049         )
00050         {
00051             // Determine if the test has been disabled.
00052             static const char* disabledPrefix = "DISABLED_";
00053             bool isDisabled = ((::strlen(testName) >= 9) &&
00054                                (!::memcmp(testName, disabledPrefix, 9)));
00055 
00056             if (isDisabled)
00057             {
00058                 testName += 9;
00059             }
00060 
00061             // Add the descriptor.
00062             TestDescriptor* descriptor = new TestDescriptor(fixtureName,
00063                     testName,
00064                     runs,
00065                     iterations,
00066                     testFactory,
00067                     parameters,
00068                     isDisabled);
00069 
00070             Instance()._tests.push_back(descriptor);
00071 
00072             return descriptor;
00073         }
00074 
00075 
00077 
00080         static void AddOutputter(Outputter& outputter)
00081         {
00082             Instance()._outputters.push_back(&outputter);
00083         }
00084 
00085 
00087 
00093         static void ApplyPatternFilter(const char* pattern)
00094         {
00095             Benchmarker& instance = Instance();
00096 
00097             // Split the filter at '-' if it exists.
00098             const char* const dash = strchr(pattern, '-');
00099 
00100             std::string positive;
00101             std::string negative;
00102 
00103             if (dash == NULL)
00104             {
00105                 positive = pattern;
00106             }
00107             else
00108             {
00109                 positive = std::string(pattern, dash);
00110                 negative = std::string(dash + 1);
00111 
00112                 if (positive.empty())
00113                 {
00114                     positive = "*";
00115                 }
00116             }
00117 
00118             // Iterate across all tests and test them against the patterns.
00119             std::size_t index = 0;
00120 
00121             while (index < instance._tests.size())
00122             {
00123                 TestDescriptor* desc = instance._tests[index];
00124 
00125                 if ((!FilterMatchesString(positive.c_str(),
00126                                           desc->CanonicalName)) ||
00127                         (FilterMatchesString(negative.c_str(),
00128                                              desc->CanonicalName)))
00129                 {
00130                     instance._tests.erase(
00131                         instance._tests.begin() +
00132                         std::vector<TestDescriptor*>::difference_type(index)
00133                     );
00134                     delete desc;
00135                 }
00136                 else
00137                 {
00138                     ++index;
00139                 }
00140             }
00141         }
00142 
00143 
00145         static void RunAllTests()
00146         {
00147             ConsoleOutputter defaultOutputter;
00148             std::vector<Outputter*> defaultOutputters;
00149             defaultOutputters.push_back(&defaultOutputter);
00150 
00151             Benchmarker& instance = Instance();
00152             std::vector<Outputter*>& outputters =
00153                 (instance._outputters.empty() ?
00154                  defaultOutputters :
00155                  instance._outputters);
00156 
00157             // Get the tests for execution.
00158             std::vector<TestDescriptor*> tests = instance.GetTests();
00159 
00160             const std::size_t totalCount = tests.size();
00161             std::size_t disabledCount = 0;
00162 
00163             std::vector<TestDescriptor*>::const_iterator testsIt =
00164                 tests.begin();
00165 
00166             while (testsIt != tests.end())
00167             {
00168                 if ((*testsIt)->IsDisabled)
00169                 {
00170                     ++disabledCount;
00171                 }
00172 
00173                 ++testsIt;
00174             }
00175 
00176             const std::size_t enabledCount = totalCount - disabledCount;
00177 
00178             // Calibrate the tests.
00179             const CalibrationModel calibrationModel = GetCalibrationModel();
00180 
00181             // Begin output.
00182             for (std::size_t outputterIndex = 0;
00183                     outputterIndex < outputters.size();
00184                     outputterIndex++)
00185             {
00186                 outputters[outputterIndex]->Begin(enabledCount, disabledCount);
00187             }
00188 
00189             // Run through all the tests in ascending order.
00190             std::size_t index = 0;
00191 
00192             while (index < tests.size())
00193             {
00194                 // Get the test descriptor.
00195                 TestDescriptor* descriptor = tests[index++];
00196 
00197                 // Check if test matches include filters
00198                 if (instance._include.size() > 0)
00199                 {
00200                     bool included = false;
00201                     std::string name =
00202                         descriptor->FixtureName + "." +
00203                         descriptor->TestName;
00204 
00205                     for (std::size_t i = 0; i < instance._include.size(); i++)
00206                     {
00207                         if (name.find(instance._include[i]) !=
00208                                 std::string::npos)
00209                         {
00210                             included = true;
00211                             break;
00212                         }
00213                     }
00214 
00215                     if (!included)
00216                     {
00217                         continue;
00218                     }
00219                 }
00220 
00221                 // Check if test is not disabled.
00222                 if (descriptor->IsDisabled)
00223                 {
00224                     for (std::size_t outputterIndex = 0;
00225                             outputterIndex < outputters.size();
00226                             outputterIndex++)
00227                         outputters[outputterIndex]->SkipDisabledTest(
00228                             descriptor->FixtureName,
00229                             descriptor->TestName,
00230                             descriptor->Parameters,
00231                             descriptor->Runs,
00232                             descriptor->Iterations
00233                         );
00234 
00235                     continue;
00236                 }
00237 
00238                 // Describe the beginning of the run.
00239                 for (std::size_t outputterIndex = 0;
00240                         outputterIndex < outputters.size();
00241                         outputterIndex++)
00242                     outputters[outputterIndex]->BeginTest(
00243                         descriptor->FixtureName,
00244                         descriptor->TestName,
00245                         descriptor->Parameters,
00246                         descriptor->Runs,
00247                         descriptor->Iterations
00248                     );
00249 
00250                 // Execute each individual run.
00251                 std::vector<uint64_t> runTimes(descriptor->Runs);
00252                 uint64_t overheadCalibration =
00253                     calibrationModel.GetCalibration(descriptor->Iterations);
00254 
00255                 std::size_t run = 0;
00256 
00257                 while (run < descriptor->Runs)
00258                 {
00259                     // Construct a test instance.
00260                     Test* test = descriptor->Factory->CreateTest();
00261 
00262                     // Run the test.
00263                     uint64_t time = test->Run(descriptor->Iterations);
00264 
00265                     // Store the test time.
00266                     runTimes[run] = (time > overheadCalibration ?
00267                                      time - overheadCalibration :
00268                                      0);
00269 
00270                     // Dispose of the test instance.
00271                     delete test;
00272 
00273                     ++run;
00274                 }
00275 
00276                 // Calculate the test result.
00277                 TestResult testResult(runTimes, descriptor->Iterations);
00278 
00279                 // Describe the end of the run.
00280                 for (std::size_t outputterIndex = 0;
00281                         outputterIndex < outputters.size();
00282                         outputterIndex++)
00283                     outputters[outputterIndex]->EndTest(
00284                         descriptor->FixtureName,
00285                         descriptor->TestName,
00286                         descriptor->Parameters,
00287                         testResult
00288                     );
00289 
00290             }
00291 
00292             // End output.
00293             for (std::size_t outputterIndex = 0;
00294                     outputterIndex < outputters.size();
00295                     outputterIndex++)
00296                 outputters[outputterIndex]->End(enabledCount,
00297                                                 disabledCount);
00298         }
00299 
00300 
00302         static std::vector<const TestDescriptor*> ListTests()
00303         {
00304             std::vector<const TestDescriptor*> tests;
00305             Benchmarker& instance = Instance();
00306 
00307             std::size_t index = 0;
00308 
00309             while (index < instance._tests.size())
00310             {
00311                 tests.push_back(instance._tests[index++]);
00312             }
00313 
00314             return tests;
00315         }
00316 
00317 
00319 
00321         static void ShuffleTests()
00322         {
00323             Benchmarker& instance = Instance();
00324             std::random_shuffle(instance._tests.begin(),
00325                                 instance._tests.end());
00326         }
00327     private:
00329 
00331         struct CalibrationModel
00332         {
00333         public:
00334             CalibrationModel(std::size_t scale,
00335                              uint64_t slope,
00336                              uint64_t yIntercept)
00337                 :   Scale(scale),
00338                     Slope(slope),
00339                     YIntercept(yIntercept)
00340             {
00341 
00342             }
00343 
00344 
00346 
00348             const std::size_t Scale;
00349 
00350 
00352             const uint64_t Slope;
00353 
00354 
00356             const uint64_t YIntercept;
00357 
00358 
00360             int64_t GetCalibration(std::size_t iterations) const
00361             {
00362                 return YIntercept + (iterations * Slope) / Scale;
00363             }
00364         };
00365 
00366 
00368         Benchmarker()
00369         {
00370 
00371         }
00372 
00373 
00375         ~Benchmarker()
00376         {
00377             // Release all test descriptors.
00378             std::size_t index = _tests.size();
00379 
00380             while (index--)
00381             {
00382                 delete _tests[index];
00383             }
00384         }
00385 
00386 
00388         std::vector<TestDescriptor*> GetTests() const
00389         {
00390             std::vector<TestDescriptor*> tests;
00391 
00392             std::size_t index = 0;
00393 
00394             while (index < _tests.size())
00395             {
00396                 tests.push_back(_tests[index++]);
00397             }
00398 
00399             return tests;
00400         }
00401 
00402 
00404 
00406         static bool FilterMatchesString(const char* filter,
00407                                         const std::string& str)
00408         {
00409             const char* patternStart = filter;
00410 
00411             while (true)
00412             {
00413                 if (PatternMatchesString(patternStart, str.c_str()))
00414                 {
00415                     return true;
00416                 }
00417 
00418                 // Finds the next pattern in the filter.
00419                 patternStart = strchr(patternStart, ':');
00420 
00421                 // Returns if no more pattern can be found.
00422                 if (!patternStart)
00423                 {
00424                     return false;
00425                 }
00426 
00427                 // Skips the pattern separater (the ':' character).
00428                 patternStart++;
00429             }
00430         }
00431 
00432 
00434 
00436         static bool PatternMatchesString(const char* pattern, const char* str)
00437         {
00438             switch (*pattern)
00439             {
00440                 case '\0':
00441                 case ':':
00442                     return (*str == '\0');
00443 
00444                 case '?':  // Matches any single character.
00445                     return ((*str != '\0') &&
00446                             (PatternMatchesString(pattern + 1, str + 1)));
00447 
00448                 case '*':  // Matches any string (possibly empty) of characters.
00449                     return (((*str != '\0') &&
00450                              (PatternMatchesString(pattern, str + 1))) ||
00451                             (PatternMatchesString(pattern + 1, str)));
00452 
00453                 default:
00454                     return ((*pattern == *str) &&
00455                             (PatternMatchesString(pattern + 1, str + 1)));
00456             }
00457         }
00458 
00459 
00461 
00463         static CalibrationModel GetCalibrationModel()
00464         {
00465             // We perform a number of runs of varying iterations with an empty
00466             // test body. The assumption here is, that the time taken for the
00467             // test run is linear with regards to the number of iterations, ie.
00468             // some constant overhead with a per-iteration overhead. This
00469             // hypothesis has been manually validated by linear regression over
00470             // sample data.
00471             //
00472             // In order to avoid losing too much precision, we are going to
00473             // calibrate in terms of the overhead of some x n iterations,
00474             // where n must be a sufficiently large number to produce some
00475             // significant runtime. On a high-end 2012 Retina MacBook Pro with
00476             // -O3 on clang-602.0.53 (LLVM 6.1.0) n = 1,000,000 produces
00477             // run times of ~1.9 ms, which should be sufficiently precise.
00478             //
00479             // However, as the constant overhead is mostly related to
00480             // retrieving the system clock, which under the same conditions
00481             // clocks in at around 17 ms, we run the risk of winding up with
00482             // a negative y-intercept if we do not fix the y-intercept. This
00483             // intercept is therefore fixed by a large number of runs of 0
00484             // iterations.
00485             ::hayai::Test* test = new Test();
00486 
00487 #define HAYAI_CALIBRATION_INTERESECT_RUNS 10000
00488 
00489 #define HAYAI_CALIBRATION_RUNS 10
00490 #define HAYAI_CALIBRATION_SCALE 1000000
00491 #define HAYAI_CALIBRATION_PPR 6
00492 
00493             // Determine the intercept.
00494             uint64_t
00495             interceptSum = 0,
00496             interceptMin = std::numeric_limits<uint64_t>::min(),
00497             interceptMax = 0;
00498 
00499             for (std::size_t run = 0;
00500                     run < HAYAI_CALIBRATION_INTERESECT_RUNS;
00501                     ++run)
00502             {
00503                 uint64_t intercept = test->Run(0);
00504                 interceptSum += intercept;
00505 
00506                 if (intercept < interceptMin)
00507                 {
00508                     interceptMin = intercept;
00509                 }
00510 
00511                 if (intercept > interceptMax)
00512                 {
00513                     interceptMax = intercept;
00514                 }
00515             }
00516 
00517             uint64_t interceptAvg =
00518                 interceptSum / HAYAI_CALIBRATION_INTERESECT_RUNS;
00519 
00520             // Produce a series of sample points.
00521             std::vector<uint64_t> x(HAYAI_CALIBRATION_RUNS *
00522                                     HAYAI_CALIBRATION_PPR);
00523             std::vector<uint64_t> t(HAYAI_CALIBRATION_RUNS *
00524                                     HAYAI_CALIBRATION_PPR);
00525 
00526             std::size_t point = 0;
00527 
00528             for (std::size_t run = 0; run < HAYAI_CALIBRATION_RUNS; ++run)
00529             {
00530 #define HAYAI_CALIBRATION_POINT(_x)                                     \
00531                 x[point] = _x;                                          \
00532                 t[point++] =                                            \
00533                     test->Run(_x * std::size_t(HAYAI_CALIBRATION_SCALE))
00534 
00535                 HAYAI_CALIBRATION_POINT(1);
00536                 HAYAI_CALIBRATION_POINT(2);
00537                 HAYAI_CALIBRATION_POINT(5);
00538                 HAYAI_CALIBRATION_POINT(10);
00539                 HAYAI_CALIBRATION_POINT(15);
00540                 HAYAI_CALIBRATION_POINT(20);
00541 
00542 #undef HAYAI_CALIBRATION_POINT
00543             }
00544 
00545             // As we have a fixed y-intercept, b, the optimal slope for a line
00546             // fitting the sample points will be
00547             // $\frac {\sum_{i=1}^{n} x_n \cdot (y_n - b)}
00548             //  {\sum_{i=1}^{n} {x_n}^2}$.
00549             uint64_t
00550             sumProducts = 0,
00551             sumXSquared = 0;
00552 
00553             std::size_t p = x.size();
00554 
00555             while (p--)
00556             {
00557                 sumXSquared += x[p] * x[p];
00558                 sumProducts += x[p] * (t[p] - interceptAvg);
00559             }
00560 
00561             uint64_t slope = sumProducts / sumXSquared;
00562 
00563             delete test;
00564 
00565             return CalibrationModel(HAYAI_CALIBRATION_SCALE,
00566                                     slope,
00567                                     interceptAvg);
00568 
00569 #undef HAYAI_CALIBRATION_INTERESECT_RUNS
00570 
00571 #undef HAYAI_CALIBRATION_RUNS
00572 #undef HAYAI_CALIBRATION_SCALE
00573 #undef HAYAI_CALIBRATION_PPR
00574         }
00575 
00576 
00577         std::vector<Outputter*> _outputters; 
00578         std::vector<TestDescriptor*> _tests; 
00579         std::vector<std::string> _include; 
00580     };
00581 }
00582 #endif


hayai
Author(s): Nick Bruun
autogenerated on Thu Jun 6 2019 18:13:43