Merge pull request #3342 from alalek:perf_stability_check
This commit is contained in:
commit
1fbad26fe3
@ -419,9 +419,11 @@ private:
|
|||||||
static int64 timeLimitDefault;
|
static int64 timeLimitDefault;
|
||||||
static unsigned int iterationsLimitDefault;
|
static unsigned int iterationsLimitDefault;
|
||||||
|
|
||||||
|
unsigned int minIters;
|
||||||
unsigned int nIters;
|
unsigned int nIters;
|
||||||
unsigned int currentIter;
|
unsigned int currentIter;
|
||||||
unsigned int runsPerIteration;
|
unsigned int runsPerIteration;
|
||||||
|
unsigned int perfValidationStage;
|
||||||
|
|
||||||
performance_metrics metrics;
|
performance_metrics metrics;
|
||||||
void validateMetrics();
|
void validateMetrics();
|
||||||
|
@ -1,5 +1,16 @@
|
|||||||
#include "precomp.hpp"
|
#include "precomp.hpp"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#if defined WIN32 || defined _WIN32 || defined WIN64 || defined _WIN64
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_CUDA
|
#ifdef HAVE_CUDA
|
||||||
#include "opencv2/core/cuda.hpp"
|
#include "opencv2/core/cuda.hpp"
|
||||||
#endif
|
#endif
|
||||||
@ -35,11 +46,11 @@ static bool param_verify_sanity;
|
|||||||
static bool param_collect_impl;
|
static bool param_collect_impl;
|
||||||
#endif
|
#endif
|
||||||
extern bool test_ipp_check;
|
extern bool test_ipp_check;
|
||||||
|
|
||||||
#ifdef HAVE_CUDA
|
#ifdef HAVE_CUDA
|
||||||
static int param_cuda_device;
|
static int param_cuda_device;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
static int param_affinity_mask;
|
static int param_affinity_mask;
|
||||||
static bool log_power_checkpoints;
|
static bool log_power_checkpoints;
|
||||||
@ -59,6 +70,8 @@ static void setCurrentThreadAffinityMask(int mask)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static double perf_stability_criteria = 0.03; // 3%
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class PerfEnvironment: public ::testing::Environment
|
class PerfEnvironment: public ::testing::Environment
|
||||||
@ -635,6 +648,82 @@ void performance_metrics::clear()
|
|||||||
terminationReason = TERM_UNKNOWN;
|
terminationReason = TERM_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************************\
|
||||||
|
* Performance validation results
|
||||||
|
\*****************************************************************************************/
|
||||||
|
|
||||||
|
static bool perf_validation_enabled = false;
|
||||||
|
|
||||||
|
static std::string perf_validation_results_directory;
|
||||||
|
static std::map<std::string, float> perf_validation_results;
|
||||||
|
static std::string perf_validation_results_outfile;
|
||||||
|
|
||||||
|
static double perf_validation_criteria = 0.03; // 3 %
|
||||||
|
static double perf_validation_time_threshold_ms = 0.1;
|
||||||
|
static int perf_validation_idle_delay_ms = 3000; // 3 sec
|
||||||
|
|
||||||
|
static void loadPerfValidationResults(const std::string& fileName)
|
||||||
|
{
|
||||||
|
perf_validation_results.clear();
|
||||||
|
std::ifstream infile(fileName.c_str());
|
||||||
|
while (!infile.eof())
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
float value = 0;
|
||||||
|
if (!(infile >> value))
|
||||||
|
{
|
||||||
|
if (infile.eof())
|
||||||
|
break; // it is OK
|
||||||
|
std::cout << "ERROR: Can't load performance validation results from " << fileName << "!" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
infile.ignore(1);
|
||||||
|
if (!(std::getline(infile, name)))
|
||||||
|
{
|
||||||
|
std::cout << "ERROR: Can't load performance validation results from " << fileName << "!" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!name.empty() && name[name.size() - 1] == '\r') // CRLF processing on Linux
|
||||||
|
name.resize(name.size() - 1);
|
||||||
|
perf_validation_results[name] = value;
|
||||||
|
}
|
||||||
|
std::cout << "Performance validation results loaded from " << fileName << " (" << perf_validation_results.size() << " entries)" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void savePerfValidationResult(const std::string& name, float value)
|
||||||
|
{
|
||||||
|
perf_validation_results[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void savePerfValidationResults()
|
||||||
|
{
|
||||||
|
if (!perf_validation_results_outfile.empty())
|
||||||
|
{
|
||||||
|
std::ofstream outfile((perf_validation_results_directory + perf_validation_results_outfile).c_str());
|
||||||
|
std::map<std::string, float>::const_iterator i;
|
||||||
|
for (i = perf_validation_results.begin(); i != perf_validation_results.end(); ++i)
|
||||||
|
{
|
||||||
|
outfile << i->second << ';';
|
||||||
|
outfile << i->first << std::endl;
|
||||||
|
}
|
||||||
|
outfile.close();
|
||||||
|
std::cout << "Performance validation results saved (" << perf_validation_results.size() << " entries)" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PerfValidationEnvironment : public ::testing::Environment
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~PerfValidationEnvironment() {}
|
||||||
|
virtual void SetUp() {}
|
||||||
|
|
||||||
|
virtual void TearDown()
|
||||||
|
{
|
||||||
|
savePerfValidationResults();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*****************************************************************************************\
|
/*****************************************************************************************\
|
||||||
* ::perf::TestBase
|
* ::perf::TestBase
|
||||||
@ -666,6 +755,8 @@ void TestBase::Init(const std::vector<std::string> & availableImpls,
|
|||||||
"{ perf_list_impls |false |list available implementation variants and exit}"
|
"{ perf_list_impls |false |list available implementation variants and exit}"
|
||||||
"{ perf_run_cpu |false |deprecated, equivalent to --perf_impl=plain}"
|
"{ perf_run_cpu |false |deprecated, equivalent to --perf_impl=plain}"
|
||||||
"{ perf_strategy |default |specifies performance measuring strategy: default, base or simple (weak restrictions)}"
|
"{ perf_strategy |default |specifies performance measuring strategy: default, base or simple (weak restrictions)}"
|
||||||
|
"{ perf_read_validation_results | |specifies file name with performance results from previous run}"
|
||||||
|
"{ perf_write_validation_results | |specifies file name to write performance validation results}"
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
"{ perf_time_limit |6.0 |default time limit for a single test (in seconds)}"
|
"{ perf_time_limit |6.0 |default time limit for a single test (in seconds)}"
|
||||||
"{ perf_affinity_mask |0 |set affinity mask for the main thread}"
|
"{ perf_affinity_mask |0 |set affinity mask for the main thread}"
|
||||||
@ -789,6 +880,26 @@ void TestBase::Init(const std::vector<std::string> & availableImpls,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
{
|
||||||
|
const char* path = getenv("OPENCV_PERF_VALIDATION_DIR");
|
||||||
|
if (path)
|
||||||
|
perf_validation_results_directory = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string fileName_perf_validation_results_src = args.get<std::string>("perf_read_validation_results");
|
||||||
|
if (!fileName_perf_validation_results_src.empty())
|
||||||
|
{
|
||||||
|
perf_validation_enabled = true;
|
||||||
|
loadPerfValidationResults(perf_validation_results_directory + fileName_perf_validation_results_src);
|
||||||
|
}
|
||||||
|
|
||||||
|
perf_validation_results_outfile = args.get<std::string>("perf_write_validation_results");
|
||||||
|
if (!perf_validation_results_outfile.empty())
|
||||||
|
{
|
||||||
|
perf_validation_enabled = true;
|
||||||
|
::testing::AddGlobalTestEnvironment(new PerfValidationEnvironment());
|
||||||
|
}
|
||||||
|
|
||||||
if (!args.check())
|
if (!args.check())
|
||||||
{
|
{
|
||||||
args.printErrors();
|
args.printErrors();
|
||||||
@ -878,7 +989,9 @@ TestBase::TestBase(): testStrategy(PERF_STRATEGY_DEFAULT), declare(this)
|
|||||||
{
|
{
|
||||||
lastTime = totalTime = timeLimit = 0;
|
lastTime = totalTime = timeLimit = 0;
|
||||||
nIters = currentIter = runsPerIteration = 0;
|
nIters = currentIter = runsPerIteration = 0;
|
||||||
|
minIters = param_min_samples;
|
||||||
verified = false;
|
verified = false;
|
||||||
|
perfValidationStage = 0;
|
||||||
}
|
}
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
# pragma warning(pop)
|
# pragma warning(pop)
|
||||||
@ -1004,7 +1117,7 @@ bool TestBase::next()
|
|||||||
has_next = false;
|
has_next = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (currentIter < param_min_samples)
|
if (currentIter < minIters)
|
||||||
{
|
{
|
||||||
has_next = true;
|
has_next = true;
|
||||||
break;
|
break;
|
||||||
@ -1012,14 +1125,96 @@ bool TestBase::next()
|
|||||||
|
|
||||||
calcMetrics();
|
calcMetrics();
|
||||||
|
|
||||||
double criteria = 0.03; // 3%
|
|
||||||
if (fabs(metrics.mean) > 1e-6)
|
if (fabs(metrics.mean) > 1e-6)
|
||||||
has_next = metrics.stddev > criteria * fabs(metrics.mean);
|
has_next = metrics.stddev > perf_stability_criteria * fabs(metrics.mean);
|
||||||
else
|
else
|
||||||
has_next = true;
|
has_next = true;
|
||||||
}
|
}
|
||||||
} while (false);
|
} while (false);
|
||||||
|
|
||||||
|
if (perf_validation_enabled && !has_next)
|
||||||
|
{
|
||||||
|
calcMetrics();
|
||||||
|
double median_ms = metrics.median * 1000.0f / metrics.frequency;
|
||||||
|
|
||||||
|
const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
|
||||||
|
std::string name = (test_info == 0) ? "" :
|
||||||
|
std::string(test_info->test_case_name()) + "--" + test_info->name();
|
||||||
|
|
||||||
|
if (!perf_validation_results.empty() && !name.empty())
|
||||||
|
{
|
||||||
|
std::map<std::string, float>::iterator i = perf_validation_results.find(name);
|
||||||
|
bool isSame = false;
|
||||||
|
bool found = false;
|
||||||
|
bool grow = false;
|
||||||
|
if (i != perf_validation_results.end())
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
double prev_result = i->second;
|
||||||
|
grow = median_ms > prev_result;
|
||||||
|
isSame = fabs(median_ms - prev_result) <= perf_validation_criteria * fabs(median_ms);
|
||||||
|
if (!isSame)
|
||||||
|
{
|
||||||
|
if (perfValidationStage == 0)
|
||||||
|
{
|
||||||
|
printf("Performance is changed (samples = %d, median):\n %.2f ms (current)\n %.2f ms (previous)\n", (int)times.size(), median_ms, prev_result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (perfValidationStage == 0)
|
||||||
|
printf("New performance result is detected\n");
|
||||||
|
}
|
||||||
|
if (!isSame)
|
||||||
|
{
|
||||||
|
if (perfValidationStage < 2)
|
||||||
|
{
|
||||||
|
if (perfValidationStage == 0 && currentIter <= minIters * 3 && currentIter < nIters)
|
||||||
|
{
|
||||||
|
unsigned int new_minIters = std::max(minIters * 5, currentIter * 3);
|
||||||
|
printf("Increase minIters from %u to %u\n", minIters, new_minIters);
|
||||||
|
minIters = new_minIters;
|
||||||
|
has_next = true;
|
||||||
|
perfValidationStage++;
|
||||||
|
}
|
||||||
|
else if (found && currentIter >= nIters &&
|
||||||
|
median_ms > perf_validation_time_threshold_ms &&
|
||||||
|
(grow || metrics.stddev > perf_stability_criteria * fabs(metrics.mean)))
|
||||||
|
{
|
||||||
|
printf("Performance is unstable, it may be a result of overheat problems\n");
|
||||||
|
printf("Idle delay for %d ms... \n", perf_validation_idle_delay_ms);
|
||||||
|
#if defined WIN32 || defined _WIN32 || defined WIN64 || defined _WIN64
|
||||||
|
Sleep(perf_validation_idle_delay_ms);
|
||||||
|
#else
|
||||||
|
usleep(perf_validation_idle_delay_ms * 1000);
|
||||||
|
#endif
|
||||||
|
has_next = true;
|
||||||
|
minIters = std::min(minIters * 5, nIters);
|
||||||
|
// reset collected samples
|
||||||
|
currentIter = 0;
|
||||||
|
times.clear();
|
||||||
|
metrics.clear();
|
||||||
|
perfValidationStage += 2;
|
||||||
|
}
|
||||||
|
if (!has_next)
|
||||||
|
{
|
||||||
|
printf("Assume that current result is valid\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Re-measured performance result: %.2f ms\n", median_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!has_next && !name.empty())
|
||||||
|
{
|
||||||
|
savePerfValidationResult(name, (float)median_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
if (log_power_checkpoints)
|
if (log_power_checkpoints)
|
||||||
{
|
{
|
||||||
@ -1223,9 +1418,10 @@ void TestBase::validateMetrics()
|
|||||||
else if (getCurrentPerformanceStrategy() == PERF_STRATEGY_SIMPLE)
|
else if (getCurrentPerformanceStrategy() == PERF_STRATEGY_SIMPLE)
|
||||||
{
|
{
|
||||||
double mean = metrics.mean * 1000.0f / metrics.frequency;
|
double mean = metrics.mean * 1000.0f / metrics.frequency;
|
||||||
|
double median = metrics.median * 1000.0f / metrics.frequency;
|
||||||
double stddev = metrics.stddev * 1000.0f / metrics.frequency;
|
double stddev = metrics.stddev * 1000.0f / metrics.frequency;
|
||||||
double percents = stddev / mean * 100.f;
|
double percents = stddev / mean * 100.f;
|
||||||
printf("[ PERFSTAT ] (samples = %d, mean = %.2f, stddev = %.2f (%.1f%%))\n", (int)metrics.samples, mean, stddev, percents);
|
printf("[ PERFSTAT ] (samples = %d, mean = %.2f, median = %.2f, stddev = %.2f (%.1f%%))\n", (int)metrics.samples, mean, median, stddev, percents);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user