4 #include "opencv2/core/cuda.hpp"
13 int64 TestBase::timeLimitDefault = 0;
14 unsigned int TestBase::iterationsLimitDefault = (unsigned int)(-1);
15 int64 TestBase::_timeadjustment = 0;
17 // Item [0] will be considered the default implementation.
18 static std::vector<std::string> available_impls;
20 static std::string param_impl;
22 static enum PERF_STRATEGY param_strategy = PERF_STRATEGY_BASE;
24 static double param_max_outliers;
25 static double param_max_deviation;
26 static unsigned int param_min_samples;
27 static unsigned int param_force_samples;
28 static uint64 param_seed;
29 static double param_time_limit;
30 static int param_threads;
31 static bool param_write_sanity;
32 static bool param_verify_sanity;
34 static int param_cuda_device;
39 static int param_affinity_mask;
40 static bool log_power_checkpoints;
42 #include <sys/syscall.h>
44 static void setCurrentThreadAffinityMask(int mask)
47 int syscallres=syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask);
51 err=err;//to avoid warnings about unused variables
52 LOGE("Error in the syscall setaffinity: mask=%d=0x%x err=%d=0x%x", mask, mask, err, err);
59 class PerfEnvironment: public ::testing::Environment
64 cv::setNumThreads(-1);
70 static void randu(cv::Mat& m)
72 const int bigValue = 0x00000FFF;
73 if (m.depth() < CV_32F)
75 int minmax[] = {0, 256};
76 cv::Mat mr = cv::Mat(m.rows, (int)(m.cols * m.elemSize()), CV_8U, m.ptr(), m.step[0]);
77 cv::randu(mr, cv::Mat(1, 1, CV_32S, minmax), cv::Mat(1, 1, CV_32S, minmax + 1));
79 else if (m.depth() == CV_32F)
81 //float minmax[] = {-FLT_MAX, FLT_MAX};
82 float minmax[] = {-bigValue, bigValue};
83 cv::Mat mr = m.reshape(1);
84 cv::randu(mr, cv::Mat(1, 1, CV_32F, minmax), cv::Mat(1, 1, CV_32F, minmax + 1));
88 //double minmax[] = {-DBL_MAX, DBL_MAX};
89 double minmax[] = {-bigValue, bigValue};
90 cv::Mat mr = m.reshape(1);
91 cv::randu(mr, cv::Mat(1, 1, CV_64F, minmax), cv::Mat(1, 1, CV_64F, minmax + 1));
95 /*****************************************************************************************\
96 * inner exception class for early termination
97 \*****************************************************************************************/
99 class PerfEarlyExitException: public cv::Exception {};
101 /*****************************************************************************************\
103 \*****************************************************************************************/
105 Regression& Regression::instance()
107 static Regression single;
111 Regression& Regression::add(TestBase* test, const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)
113 if(test) test->setVerified();
114 return instance()(name, array, eps, err);
117 Regression& Regression::addKeypoints(TestBase* test, const std::string& name, const std::vector<cv::KeyPoint>& array, double eps, ERROR_TYPE err)
119 int len = (int)array.size();
120 cv::Mat pt (len, 1, CV_32FC2, len ? (void*)&array[0].pt : 0, sizeof(cv::KeyPoint));
121 cv::Mat size (len, 1, CV_32FC1, len ? (void*)&array[0].size : 0, sizeof(cv::KeyPoint));
122 cv::Mat angle (len, 1, CV_32FC1, len ? (void*)&array[0].angle : 0, sizeof(cv::KeyPoint));
123 cv::Mat response(len, 1, CV_32FC1, len ? (void*)&array[0].response : 0, sizeof(cv::KeyPoint));
124 cv::Mat octave (len, 1, CV_32SC1, len ? (void*)&array[0].octave : 0, sizeof(cv::KeyPoint));
125 cv::Mat class_id(len, 1, CV_32SC1, len ? (void*)&array[0].class_id : 0, sizeof(cv::KeyPoint));
127 return Regression::add(test, name + "-pt", pt, eps, ERROR_ABSOLUTE)
128 (name + "-size", size, eps, ERROR_ABSOLUTE)
129 (name + "-angle", angle, eps, ERROR_ABSOLUTE)
130 (name + "-response", response, eps, err)
131 (name + "-octave", octave, eps, ERROR_ABSOLUTE)
132 (name + "-class_id", class_id, eps, ERROR_ABSOLUTE);
135 Regression& Regression::addMatches(TestBase* test, const std::string& name, const std::vector<cv::DMatch>& array, double eps, ERROR_TYPE err)
137 int len = (int)array.size();
138 cv::Mat queryIdx(len, 1, CV_32SC1, len ? (void*)&array[0].queryIdx : 0, sizeof(cv::DMatch));
139 cv::Mat trainIdx(len, 1, CV_32SC1, len ? (void*)&array[0].trainIdx : 0, sizeof(cv::DMatch));
140 cv::Mat imgIdx (len, 1, CV_32SC1, len ? (void*)&array[0].imgIdx : 0, sizeof(cv::DMatch));
141 cv::Mat distance(len, 1, CV_32FC1, len ? (void*)&array[0].distance : 0, sizeof(cv::DMatch));
143 return Regression::add(test, name + "-queryIdx", queryIdx, DBL_EPSILON, ERROR_ABSOLUTE)
144 (name + "-trainIdx", trainIdx, DBL_EPSILON, ERROR_ABSOLUTE)
145 (name + "-imgIdx", imgIdx, DBL_EPSILON, ERROR_ABSOLUTE)
146 (name + "-distance", distance, eps, err);
149 void Regression::Init(const std::string& testSuitName, const std::string& ext)
151 instance().init(testSuitName, ext);
154 void Regression::init(const std::string& testSuitName, const std::string& ext)
156 if (!storageInPath.empty())
158 LOGE("Subsequent initialization of Regression utility is not allowed.");
162 const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");
163 const char *path_separator = "/";
167 int len = (int)strlen(data_path_dir)-1;
168 if (len < 0) len = 0;
169 std::string path_base = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))
170 + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator)
174 storageInPath = path_base + testSuitName + ext;
175 storageOutPath = path_base + testSuitName;
179 storageInPath = testSuitName + ext;
180 storageOutPath = testSuitName;
183 suiteName = testSuitName;
187 if (storageIn.open(storageInPath, cv::FileStorage::READ))
189 rootIn = storageIn.root();
190 if (storageInPath.length() > 3 && storageInPath.substr(storageInPath.length()-3) == ".gz")
191 storageOutPath += "_new";
192 storageOutPath += ext;
195 catch(cv::Exception&)
197 LOGE("Failed to open sanity data for reading: %s", storageInPath.c_str());
200 if(!storageIn.isOpened())
201 storageOutPath = storageInPath;
204 Regression::Regression() : regRNG(cv::getTickCount())//this rng should be really random
208 Regression::~Regression()
210 if (storageIn.isOpened())
212 if (storageOut.isOpened())
214 if (!currentTestNodeName.empty())
216 storageOut.release();
220 cv::FileStorage& Regression::write()
222 if (!storageOut.isOpened() && !storageOutPath.empty())
224 int mode = (storageIn.isOpened() && storageInPath == storageOutPath)
225 ? cv::FileStorage::APPEND : cv::FileStorage::WRITE;
226 storageOut.open(storageOutPath, mode);
227 if (!storageOut.isOpened())
229 LOGE("Could not open \"%s\" file for writing", storageOutPath.c_str());
230 storageOutPath.clear();
232 else if (mode == cv::FileStorage::WRITE && !rootIn.empty())
234 //TODO: write content of rootIn node into the storageOut
240 std::string Regression::getCurrentTestNodeName()
242 const ::testing::TestInfo* const test_info =
243 ::testing::UnitTest::GetInstance()->current_test_info();
248 std::string nodename = std::string(test_info->test_case_name()) + "--" + test_info->name();
249 size_t idx = nodename.find_first_of('/');
250 if (idx != std::string::npos)
253 const char* type_param = test_info->type_param();
255 (nodename += "--") += type_param;
257 const char* value_param = test_info->value_param();
258 if (value_param != 0)
259 (nodename += "--") += value_param;
261 for(size_t i = 0; i < nodename.length(); ++i)
262 if (!isalnum(nodename[i]) && '_' != nodename[i])
268 bool Regression::isVector(cv::InputArray a)
270 return a.kind() == cv::_InputArray::STD_VECTOR_MAT || a.kind() == cv::_InputArray::STD_VECTOR_VECTOR;
273 double Regression::getElem(cv::Mat& m, int y, int x, int cn)
277 case CV_8U: return *(m.ptr<unsigned char>(y, x) + cn);
278 case CV_8S: return *(m.ptr<signed char>(y, x) + cn);
279 case CV_16U: return *(m.ptr<unsigned short>(y, x) + cn);
280 case CV_16S: return *(m.ptr<signed short>(y, x) + cn);
281 case CV_32S: return *(m.ptr<signed int>(y, x) + cn);
282 case CV_32F: return *(m.ptr<float>(y, x) + cn);
283 case CV_64F: return *(m.ptr<double>(y, x) + cn);
288 void Regression::write(cv::Mat m)
290 if (!m.empty() && m.dims < 2) return;
293 cv::minMaxIdx(m, &min, &max);
294 write() << "min" << min << "max" << max;
296 write() << "last" << "{" << "x" << m.size.p[1] - 1 << "y" << m.size.p[0] - 1
297 << "val" << getElem(m, m.size.p[0] - 1, m.size.p[1] - 1, m.channels() - 1) << "}";
300 x = regRNG.uniform(0, m.size.p[1]);
301 y = regRNG.uniform(0, m.size.p[0]);
302 cn = regRNG.uniform(0, m.channels());
303 write() << "rng1" << "{" << "x" << x << "y" << y;
304 if(cn > 0) write() << "cn" << cn;
305 write() << "val" << getElem(m, y, x, cn) << "}";
307 x = regRNG.uniform(0, m.size.p[1]);
308 y = regRNG.uniform(0, m.size.p[0]);
309 cn = regRNG.uniform(0, m.channels());
310 write() << "rng2" << "{" << "x" << x << "y" << y;
311 if (cn > 0) write() << "cn" << cn;
312 write() << "val" << getElem(m, y, x, cn) << "}";
315 void Regression::verify(cv::FileNode node, cv::Mat actual, double eps, std::string argname, ERROR_TYPE err)
317 if (!actual.empty() && actual.dims < 2) return;
319 double expect_min = (double)node["min"];
320 double expect_max = (double)node["max"];
322 if (err == ERROR_RELATIVE)
323 eps *= std::max(std::abs(expect_min), std::abs(expect_max));
325 double actual_min, actual_max;
326 cv::minMaxIdx(actual, &actual_min, &actual_max);
328 ASSERT_NEAR(expect_min, actual_min, eps)
329 << argname << " has unexpected minimal value" << std::endl;
330 ASSERT_NEAR(expect_max, actual_max, eps)
331 << argname << " has unexpected maximal value" << std::endl;
333 cv::FileNode last = node["last"];
334 double actual_last = getElem(actual, actual.size.p[0] - 1, actual.size.p[1] - 1, actual.channels() - 1);
335 int expect_cols = (int)last["x"] + 1;
336 int expect_rows = (int)last["y"] + 1;
337 ASSERT_EQ(expect_cols, actual.size.p[1])
338 << argname << " has unexpected number of columns" << std::endl;
339 ASSERT_EQ(expect_rows, actual.size.p[0])
340 << argname << " has unexpected number of rows" << std::endl;
342 double expect_last = (double)last["val"];
343 ASSERT_NEAR(expect_last, actual_last, eps)
344 << argname << " has unexpected value of the last element" << std::endl;
346 cv::FileNode rng1 = node["rng1"];
349 int cn1 = rng1["cn"];
351 double expect_rng1 = (double)rng1["val"];
352 // it is safe to use x1 and y1 without checks here because we have already
353 // verified that mat size is the same as recorded
354 double actual_rng1 = getElem(actual, y1, x1, cn1);
356 ASSERT_NEAR(expect_rng1, actual_rng1, eps)
357 << argname << " has unexpected value of the ["<< x1 << ":" << y1 << ":" << cn1 <<"] element" << std::endl;
359 cv::FileNode rng2 = node["rng2"];
362 int cn2 = rng2["cn"];
364 double expect_rng2 = (double)rng2["val"];
365 double actual_rng2 = getElem(actual, y2, x2, cn2);
367 ASSERT_NEAR(expect_rng2, actual_rng2, eps)
368 << argname << " has unexpected value of the ["<< x2 << ":" << y2 << ":" << cn2 <<"] element" << std::endl;
371 void Regression::write(cv::InputArray array)
373 write() << "kind" << array.kind();
374 write() << "type" << array.type();
377 int total = (int)array.total();
378 int idx = regRNG.uniform(0, total);
379 write() << "len" << total;
380 write() << "idx" << idx;
382 cv::Mat m = array.getMat(idx);
384 if (m.total() * m.channels() < 26) //5x5 or smaller
385 write() << "val" << m;
391 if (array.total() * array.channels() < 26) //5x5 or smaller
392 write() << "val" << array.getMat();
394 write(array.getMat());
398 static int countViolations(const cv::Mat& expected, const cv::Mat& actual, const cv::Mat& diff, double eps, double* max_violation = 0, double* max_allowed = 0)
401 diff.reshape(1).convertTo(diff64f, CV_64F);
403 cv::Mat expected_abs = cv::abs(expected.reshape(1));
404 cv::Mat actual_abs = cv::abs(actual.reshape(1));
405 cv::Mat maximum, mask;
406 cv::max(expected_abs, actual_abs, maximum);
407 cv::multiply(maximum, cv::Vec<double, 1>(eps), maximum, CV_64F);
408 cv::compare(diff64f, maximum, mask, cv::CMP_GT);
410 int v = cv::countNonZero(mask);
412 if (v > 0 && max_violation != 0 && max_allowed != 0)
415 cv::minMaxIdx(maximum, 0, max_allowed, 0, loc, mask);
416 *max_violation = diff64f.at<double>(loc[1], loc[0]);
422 void Regression::verify(cv::FileNode node, cv::InputArray array, double eps, ERROR_TYPE err)
424 int expected_kind = (int)node["kind"];
425 int expected_type = (int)node["type"];
426 ASSERT_EQ(expected_kind, array.kind()) << " Argument \"" << node.name() << "\" has unexpected kind";
427 ASSERT_EQ(expected_type, array.type()) << " Argument \"" << node.name() << "\" has unexpected type";
429 cv::FileNode valnode = node["val"];
432 int expected_length = (int)node["len"];
433 ASSERT_EQ(expected_length, (int)array.total()) << " Vector \"" << node.name() << "\" has unexpected length";
434 int idx = node["idx"];
436 cv::Mat actual = array.getMat(idx);
438 if (valnode.isNone())
440 ASSERT_LE((size_t)26, actual.total() * (size_t)actual.channels())
441 << " \"" << node.name() << "[" << idx << "]\" has unexpected number of elements";
442 verify(node, actual, eps, cv::format("%s[%d]", node.name().c_str(), idx), err);
451 ASSERT_TRUE(actual.empty())
452 << " expected empty " << node.name() << "[" << idx<< "]";
456 ASSERT_EQ(expected.size(), actual.size())
457 << " " << node.name() << "[" << idx<< "] has unexpected size";
460 cv::absdiff(expected, actual, diff);
462 if (err == ERROR_ABSOLUTE)
464 if (!cv::checkRange(diff, true, 0, 0, eps))
466 if(expected.total() * expected.channels() < 12)
467 std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
470 cv::minMaxIdx(diff.reshape(1), 0, &max);
472 FAIL() << " Absolute difference (=" << max << ") between argument \""
473 << node.name() << "[" << idx << "]\" and expected value is greater than " << eps;
476 else if (err == ERROR_RELATIVE)
479 int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);
482 if(expected.total() * expected.channels() < 12)
483 std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
485 FAIL() << " Relative difference (" << maxv << " of " << maxa << " allowed) between argument \""
486 << node.name() << "[" << idx << "]\" and expected value is greater than " << eps << " in " << violations << " points";
494 if (valnode.isNone())
496 ASSERT_LE((size_t)26, array.total() * (size_t)array.channels())
497 << " Argument \"" << node.name() << "\" has unexpected number of elements";
498 verify(node, array.getMat(), eps, "Argument \"" + node.name() + "\"", err);
504 cv::Mat actual = array.getMat();
508 ASSERT_TRUE(actual.empty())
509 << " expected empty " << node.name();
513 ASSERT_EQ(expected.size(), actual.size())
514 << " Argument \"" << node.name() << "\" has unexpected size";
517 cv::absdiff(expected, actual, diff);
519 if (err == ERROR_ABSOLUTE)
521 if (!cv::checkRange(diff, true, 0, 0, eps))
523 if(expected.total() * expected.channels() < 12)
524 std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
527 cv::minMaxIdx(diff.reshape(1), 0, &max);
529 FAIL() << " Difference (=" << max << ") between argument1 \"" << node.name()
530 << "\" and expected value is greater than " << eps;
533 else if (err == ERROR_RELATIVE)
536 int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);
539 if(expected.total() * expected.channels() < 12)
540 std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
542 FAIL() << " Relative difference (" << maxv << " of " << maxa << " allowed) between argument \"" << node.name()
543 << "\" and expected value is greater than " << eps << " in " << violations << " points";
551 Regression& Regression::operator() (const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)
553 // exit if current test is already failed
554 if(::testing::UnitTest::GetInstance()->current_test_info()->result()->Failed()) return *this;
556 if(!array.empty() && array.depth() == CV_USRTYPE1)
558 ADD_FAILURE() << " Can not check regression for CV_USRTYPE1 data type for " << name;
562 std::string nodename = getCurrentTestNodeName();
564 cv::FileNode n = rootIn[nodename];
567 if(param_write_sanity)
569 if (nodename != currentTestNodeName)
571 if (!currentTestNodeName.empty())
573 currentTestNodeName = nodename;
575 write() << nodename << "{";
577 // TODO: verify that name is alphanumeric, current error message is useless
578 write() << name << "{";
582 else if(param_verify_sanity)
584 ADD_FAILURE() << " No regression data for " << name << " argument";
589 cv::FileNode this_arg = n[name];
590 if (!this_arg.isMap())
591 ADD_FAILURE() << " No regression data for " << name << " argument";
593 verify(this_arg, array, eps, err);
600 /*****************************************************************************************\
601 * ::perf::performance_metrics
602 \*****************************************************************************************/
603 performance_metrics::performance_metrics()
608 void performance_metrics::clear()
621 terminationReason = TERM_UNKNOWN;
625 /*****************************************************************************************\
627 \*****************************************************************************************/
630 void TestBase::Init(int argc, const char* const argv[])
632 std::vector<std::string> plain_only;
633 plain_only.push_back("plain");
634 TestBase::Init(plain_only, argc, argv);
637 void TestBase::Init(const std::vector<std::string> & availableImpls,
638 int argc, const char* const argv[])
640 available_impls = availableImpls;
642 const std::string command_line_keys =
643 "{ perf_max_outliers |8 |percent of allowed outliers}"
644 "{ perf_min_samples |10 |minimal required numer of samples}"
645 "{ perf_force_samples |100 |force set maximum number of samples for all tests}"
646 "{ perf_seed |809564 |seed for random numbers generator}"
647 "{ perf_threads |-1 |the number of worker threads, if parallel execution is enabled}"
648 "{ perf_write_sanity |false |create new records for sanity checks}"
649 "{ perf_verify_sanity |false |fail tests having no regression data for sanity checks}"
650 "{ perf_impl |" + available_impls[0] +
651 "|the implementation variant of functions under test}"
652 "{ perf_list_impls |false |list available implementation variants and exit}"
653 "{ perf_run_cpu |false |deprecated, equivalent to --perf_impl=plain}"
654 "{ perf_strategy |default |specifies performance measuring strategy: default, base or simple (weak restrictions)}"
656 "{ perf_time_limit |6.0 |default time limit for a single test (in seconds)}"
657 "{ perf_affinity_mask |0 |set affinity mask for the main thread}"
658 "{ perf_log_power_checkpoints | |additional xml logging for power measurement}"
660 "{ perf_time_limit |3.0 |default time limit for a single test (in seconds)}"
662 "{ perf_max_deviation |1.0 |}"
663 "{ help h |false |print help info}"
665 "{ perf_cuda_device |0 |run CUDA test suite onto specific CUDA capable device}"
666 "{ perf_cuda_info_only |false |print an information about system and an available CUDA devices and then exit.}"
670 cv::CommandLineParser args(argc, argv, command_line_keys);
671 if (args.has("help"))
677 ::testing::AddGlobalTestEnvironment(new PerfEnvironment);
679 param_impl = args.has("perf_run_cpu") ? "plain" : args.get<std::string>("perf_impl");
680 std::string perf_strategy = args.get<std::string>("perf_strategy");
681 if (perf_strategy == "default")
685 else if (perf_strategy == "base")
687 param_strategy = PERF_STRATEGY_BASE;
689 else if (perf_strategy == "simple")
691 param_strategy = PERF_STRATEGY_SIMPLE;
695 printf("No such strategy: %s\n", perf_strategy.c_str());
698 param_max_outliers = std::min(100., std::max(0., args.get<double>("perf_max_outliers")));
699 param_min_samples = std::max(1u, args.get<unsigned int>("perf_min_samples"));
700 param_max_deviation = std::max(0., args.get<double>("perf_max_deviation"));
701 param_seed = args.get<unsigned int>("perf_seed");
702 param_time_limit = std::max(0., args.get<double>("perf_time_limit"));
703 param_force_samples = args.get<unsigned int>("perf_force_samples");
704 param_write_sanity = args.has("perf_write_sanity");
705 param_verify_sanity = args.has("perf_verify_sanity");
706 param_threads = args.get<int>("perf_threads");
708 param_affinity_mask = args.get<int>("perf_affinity_mask");
709 log_power_checkpoints = args.has("perf_log_power_checkpoints");
712 bool param_list_impls = args.has("perf_list_impls");
714 if (param_list_impls)
716 fputs("Available implementation variants:", stdout);
717 for (size_t i = 0; i < available_impls.size(); ++i) {
719 fputs(available_impls[i].c_str(), stdout);
725 if (std::find(available_impls.begin(), available_impls.end(), param_impl) == available_impls.end())
727 printf("No such implementation: %s\n", param_impl.c_str());
733 bool printOnly = args.has("perf_cuda_info_only");
739 if (available_impls.size() > 1)
740 printf("[----------]\n[ INFO ] \tImplementation variant: %s.\n[----------]\n", param_impl.c_str()), fflush(stdout);
744 param_cuda_device = std::max(0, std::min(cv::cuda::getCudaEnabledDeviceCount(), args.get<int>("perf_cuda_device")));
746 if (param_impl == "cuda")
748 cv::cuda::DeviceInfo info(param_cuda_device);
749 if (!info.isCompatible())
751 printf("[----------]\n[ FAILURE ] \tDevice %s is NOT compatible with current CUDA module build.\n[----------]\n", info.name()), fflush(stdout);
755 cv::cuda::setDevice(param_cuda_device);
757 printf("[----------]\n[ GPU INFO ] \tRun test suite on %s GPU.\n[----------]\n", info.name()), fflush(stdout);
767 timeLimitDefault = param_time_limit == 0.0 ? 1 : (int64)(param_time_limit * cv::getTickFrequency());
768 iterationsLimitDefault = param_force_samples == 0 ? (unsigned)(-1) : param_force_samples;
769 _timeadjustment = _calibrate();
772 void TestBase::RecordRunParameters()
774 ::testing::Test::RecordProperty("cv_implementation", param_impl);
775 ::testing::Test::RecordProperty("cv_num_threads", param_threads);
778 if (param_impl == "cuda")
780 cv::cuda::DeviceInfo info(param_cuda_device);
781 ::testing::Test::RecordProperty("cv_cuda_gpu", info.name());
786 std::string TestBase::getSelectedImpl()
791 enum PERF_STRATEGY TestBase::getPerformanceStrategy()
793 return param_strategy;
796 enum PERF_STRATEGY TestBase::setPerformanceStrategy(enum PERF_STRATEGY strategy)
798 enum PERF_STRATEGY ret = param_strategy;
799 param_strategy = strategy;
804 int64 TestBase::_calibrate()
806 class _helper : public ::perf::TestBase
809 performance_metrics& getMetrics() { return calcMetrics(); }
810 virtual void TestBody() {}
811 virtual void PerfTestBody()
813 //the whole system warmup
815 cv::Mat a(2048, 2048, CV_32S, cv::Scalar(1));
816 cv::Mat b(2048, 2048, CV_32S, cv::Scalar(2));
819 for(declare.iterations(20); startTimer(), next(); stopTimer())
825 for(declare.iterations(1000); startTimer(), next(); stopTimer()){}
832 double compensation = h.getMetrics().min;
833 if (param_strategy == PERF_STRATEGY_SIMPLE)
835 CV_Assert(compensation < 0.01 * cv::getTickFrequency());
836 compensation = 0.0f; // simple strategy doesn't require any compensation
838 LOGD("Time compensation is %.0f", compensation);
839 return (int64)compensation;
843 # pragma warning(push)
844 # pragma warning(disable:4355) // 'this' : used in base member initializer list
846 TestBase::TestBase(): declare(this)
850 # pragma warning(pop)
854 void TestBase::declareArray(SizeVector& sizes, cv::InputOutputArray a, int wtype)
858 sizes.push_back(std::pair<int, cv::Size>(getSizeInBytes(a), getSize(a)));
861 else if (a.kind() != cv::_InputArray::NONE)
862 ADD_FAILURE() << " Uninitialized input/output parameters are not allowed for performance tests";
865 void TestBase::warmup(cv::InputOutputArray a, int wtype)
867 if (a.empty()) return;
868 if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
869 warmup_impl(a.getMat(), wtype);
872 size_t total = a.total();
873 for (size_t i = 0; i < total; ++i)
874 warmup_impl(a.getMat((int)i), wtype);
878 int TestBase::getSizeInBytes(cv::InputArray a)
880 if (a.empty()) return 0;
881 int total = (int)a.total();
882 if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
883 return total * CV_ELEM_SIZE(a.type());
886 for (int i = 0; i < total; ++i)
887 size += (int)a.total(i) * CV_ELEM_SIZE(a.type(i));
892 cv::Size TestBase::getSize(cv::InputArray a)
894 if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
899 bool TestBase::next()
901 static int64 lastActivityPrintTime = 0;
903 if (currentIter != (unsigned int)-1)
905 if (currentIter + 1 != times.size())
906 ADD_FAILURE() << " next() is called before stopTimer()";
910 lastActivityPrintTime = 0;
914 cv::theRNG().state = param_seed; //this rng should generate same numbers for each run
917 bool has_next = false;
920 assert(currentIter == times.size());
921 if (currentIter == 0)
927 if (param_strategy == PERF_STRATEGY_BASE)
929 has_next = currentIter < nIters && totalTime < timeLimit;
933 assert(param_strategy == PERF_STRATEGY_SIMPLE);
934 if (totalTime - lastActivityPrintTime >= cv::getTickFrequency() * 10)
936 std::cout << '.' << std::endl;
937 lastActivityPrintTime = totalTime;
939 if (currentIter >= nIters)
944 if (currentIter < param_min_samples)
952 double criteria = 0.03; // 3%
953 if (fabs(metrics.mean) > 1e-6)
954 has_next = metrics.stddev > criteria * fabs(metrics.mean);
961 if (log_power_checkpoints)
964 gettimeofday(&tim, NULL);
965 unsigned long long t1 = tim.tv_sec * 1000LLU + (unsigned long long)(tim.tv_usec / 1000.f);
967 if (currentIter == 1) RecordProperty("test_start", cv::format("%llu",t1).c_str());
968 if (!has_next) RecordProperty("test_complete", cv::format("%llu",t1).c_str());
973 startTimer(); // really we should measure activity from this moment, so reset start time
977 void TestBase::warmup_impl(cv::Mat m, int wtype)
982 cv::sum(m.reshape(1));
985 m.reshape(1).setTo(cv::Scalar::all(0));
995 unsigned int TestBase::getTotalInputSize() const
997 unsigned int res = 0;
998 for (SizeVector::const_iterator i = inputData.begin(); i != inputData.end(); ++i)
1003 unsigned int TestBase::getTotalOutputSize() const
1005 unsigned int res = 0;
1006 for (SizeVector::const_iterator i = outputData.begin(); i != outputData.end(); ++i)
1011 void TestBase::startTimer()
1013 lastTime = cv::getTickCount();
1016 void TestBase::stopTimer()
1018 int64 time = cv::getTickCount();
1020 ADD_FAILURE() << " stopTimer() is called before startTimer()/next()";
1021 lastTime = time - lastTime;
1022 totalTime += lastTime;
1023 lastTime -= _timeadjustment;
1024 if (lastTime < 0) lastTime = 0;
1025 times.push_back(lastTime);
1029 performance_metrics& TestBase::calcMetrics()
1031 CV_Assert(metrics.samples <= (unsigned int)currentIter);
1032 if ((metrics.samples == (unsigned int)currentIter) || times.size() == 0)
1035 metrics.bytesIn = getTotalInputSize();
1036 metrics.bytesOut = getTotalOutputSize();
1037 metrics.frequency = cv::getTickFrequency();
1038 metrics.samples = (unsigned int)times.size();
1039 metrics.outliers = 0;
1041 if (metrics.terminationReason != performance_metrics::TERM_INTERRUPT && metrics.terminationReason != performance_metrics::TERM_EXCEPTION)
1043 if (currentIter == nIters)
1044 metrics.terminationReason = performance_metrics::TERM_ITERATIONS;
1045 else if (totalTime >= timeLimit)
1046 metrics.terminationReason = performance_metrics::TERM_TIME;
1048 metrics.terminationReason = performance_metrics::TERM_UNKNOWN;
1051 std::sort(times.begin(), times.end());
1053 TimeVector::const_iterator start = times.begin();
1054 TimeVector::const_iterator end = times.end();
1056 if (param_strategy == PERF_STRATEGY_BASE)
1058 //estimate mean and stddev for log(time)
1062 for(TimeVector::const_iterator i = times.begin(); i != times.end(); ++i)
1064 double x = static_cast<double>(*i)/runsPerIteration;
1065 if (x < DBL_EPSILON) continue;
1069 double delta = lx - gmean;
1071 gstddev += delta * (lx - gmean);
1074 gstddev = n > 1 ? sqrt(gstddev / (n - 1)) : 0;
1076 //filter outliers assuming log-normal distribution
1077 //http://stackoverflow.com/questions/1867426/modeling-distribution-of-performance-measurements
1078 if (gstddev > DBL_EPSILON)
1080 double minout = exp(gmean - 3 * gstddev) * runsPerIteration;
1081 double maxout = exp(gmean + 3 * gstddev) * runsPerIteration;
1082 while(*start < minout) ++start, ++metrics.outliers;
1083 do --end, ++metrics.outliers; while(*end > maxout);
1084 ++end, --metrics.outliers;
1087 else if (param_strategy == PERF_STRATEGY_SIMPLE)
1089 metrics.outliers = static_cast<int>(times.size() * param_max_outliers / 100);
1090 for (unsigned int i = 0; i < metrics.outliers; i++)
1098 int offset = static_cast<int>(start - times.begin());
1100 metrics.min = static_cast<double>(*start)/runsPerIteration;
1101 //calc final metrics
1108 for(; start != end; ++start)
1110 double x = static_cast<double>(*start)/runsPerIteration;
1111 if (x > DBL_EPSILON)
1115 double gdelta = lx - gmean;
1116 gmean += gdelta / m;
1117 gstddev += gdelta * (lx - gmean);
1120 double delta = x - mean;
1122 stddev += delta * (x - mean);
1125 metrics.mean = mean;
1126 metrics.gmean = exp(gmean);
1127 metrics.gstddev = m > 1 ? sqrt(gstddev / (m - 1)) : 0;
1128 metrics.stddev = n > 1 ? sqrt(stddev / (n - 1)) : 0;
1129 metrics.median = (n % 2
1130 ? (double)times[offset + n / 2]
1131 : 0.5 * (times[offset + n / 2] + times[offset + n / 2 - 1])
1132 ) / runsPerIteration;
1137 void TestBase::validateMetrics()
1139 performance_metrics& m = calcMetrics();
1141 if (HasFailure()) return;
1143 ASSERT_GE(m.samples, 1u)
1144 << " No time measurements was performed.\nstartTimer() and stopTimer() commands are required for performance tests.";
1146 if (param_strategy == PERF_STRATEGY_BASE)
1148 EXPECT_GE(m.samples, param_min_samples)
1149 << " Only a few samples are collected.\nPlease increase number of iterations or/and time limit to get reliable performance measurements.";
1151 if (m.gstddev > DBL_EPSILON)
1153 EXPECT_GT(/*m.gmean * */1., /*m.gmean * */ 2 * sinh(m.gstddev * param_max_deviation))
1154 << " Test results are not reliable ((mean-sigma,mean+sigma) deviation interval is greater than measured time interval).";
1157 EXPECT_LE(m.outliers, std::max((unsigned int)cvCeil(m.samples * param_max_outliers / 100.), 1u))
1158 << " Test results are not reliable (too many outliers).";
1160 else if (param_strategy == PERF_STRATEGY_SIMPLE)
1162 double mean = metrics.mean * 1000.0f / metrics.frequency;
1163 double stddev = metrics.stddev * 1000.0f / metrics.frequency;
1164 double percents = stddev / mean * 100.f;
1165 printf(" samples = %d, mean = %.2f, stddev = %.2f (%.1f%%)\n", (int)metrics.samples, mean, stddev, percents);
1173 void TestBase::reportMetrics(bool toJUnitXML)
1175 performance_metrics& m = calcMetrics();
1177 if (m.terminationReason == performance_metrics::TERM_SKIP_TEST)
1181 RecordProperty("custom_status", "skipped");
1184 else if (toJUnitXML)
1186 RecordProperty("bytesIn", (int)m.bytesIn);
1187 RecordProperty("bytesOut", (int)m.bytesOut);
1188 RecordProperty("term", m.terminationReason);
1189 RecordProperty("samples", (int)m.samples);
1190 RecordProperty("outliers", (int)m.outliers);
1191 RecordProperty("frequency", cv::format("%.0f", m.frequency).c_str());
1192 RecordProperty("min", cv::format("%.0f", m.min).c_str());
1193 RecordProperty("median", cv::format("%.0f", m.median).c_str());
1194 RecordProperty("gmean", cv::format("%.0f", m.gmean).c_str());
1195 RecordProperty("gstddev", cv::format("%.6f", m.gstddev).c_str());
1196 RecordProperty("mean", cv::format("%.0f", m.mean).c_str());
1197 RecordProperty("stddev", cv::format("%.0f", m.stddev).c_str());
1201 const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
1202 const char* type_param = test_info->type_param();
1203 const char* value_param = test_info->value_param();
1205 #if defined(ANDROID) && defined(USE_ANDROID_LOGGING)
1206 LOGD("[ FAILED ] %s.%s", test_info->test_case_name(), test_info->name());
1209 if (type_param) LOGD("type = %11s", type_param);
1210 if (value_param) LOGD("params = %11s", value_param);
1212 switch (m.terminationReason)
1214 case performance_metrics::TERM_ITERATIONS:
1215 LOGD("termination reason: reached maximum number of iterations");
1217 case performance_metrics::TERM_TIME:
1218 LOGD("termination reason: reached time limit");
1220 case performance_metrics::TERM_INTERRUPT:
1221 LOGD("termination reason: aborted by the performance testing framework");
1223 case performance_metrics::TERM_EXCEPTION:
1224 LOGD("termination reason: unhandled exception");
1226 case performance_metrics::TERM_UNKNOWN:
1228 LOGD("termination reason: unknown");
1232 LOGD("bytesIn =%11lu", (unsigned long)m.bytesIn);
1233 LOGD("bytesOut =%11lu", (unsigned long)m.bytesOut);
1234 if (nIters == (unsigned int)-1 || m.terminationReason == performance_metrics::TERM_ITERATIONS)
1235 LOGD("samples =%11u", m.samples);
1237 LOGD("samples =%11u of %u", m.samples, nIters);
1238 LOGD("outliers =%11u", m.outliers);
1239 LOGD("frequency =%11.0f", m.frequency);
1242 LOGD("min =%11.0f = %.2fms", m.min, m.min * 1e3 / m.frequency);
1243 LOGD("median =%11.0f = %.2fms", m.median, m.median * 1e3 / m.frequency);
1244 LOGD("gmean =%11.0f = %.2fms", m.gmean, m.gmean * 1e3 / m.frequency);
1245 LOGD("gstddev =%11.8f = %.2fms for 97%% dispersion interval", m.gstddev, m.gmean * 2 * sinh(m.gstddev * 3) * 1e3 / m.frequency);
1246 LOGD("mean =%11.0f = %.2fms", m.mean, m.mean * 1e3 / m.frequency);
1247 LOGD("stddev =%11.0f = %.2fms", m.stddev, m.stddev * 1e3 / m.frequency);
1252 void TestBase::SetUp()
1254 cv::theRNG().state = param_seed; // this rng should generate same numbers for each run
1256 if (param_threads >= 0)
1257 cv::setNumThreads(param_threads);
1260 if (param_affinity_mask)
1261 setCurrentThreadAffinityMask(param_affinity_mask);
1267 runsPerIteration = 1;
1268 nIters = iterationsLimitDefault;
1269 currentIter = (unsigned int)-1;
1270 timeLimit = timeLimitDefault;
1274 void TestBase::TearDown()
1276 if (metrics.terminationReason == performance_metrics::TERM_SKIP_TEST)
1278 LOGI("\tTest was skipped");
1279 GTEST_SUCCEED() << "Test was skipped";
1283 if (!HasFailure() && !verified)
1284 ADD_FAILURE() << "The test has no sanity checks. There should be at least one check at the end of performance test.";
1289 reportMetrics(false);
1294 const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
1295 const char* type_param = test_info->type_param();
1296 const char* value_param = test_info->value_param();
1297 if (value_param) printf("[ VALUE ] \t%s\n", value_param), fflush(stdout);
1298 if (type_param) printf("[ TYPE ] \t%s\n", type_param), fflush(stdout);
1299 reportMetrics(true);
1302 std::string TestBase::getDataPath(const std::string& relativePath)
1304 if (relativePath.empty())
1306 ADD_FAILURE() << " Bad path to test resource";
1307 throw PerfEarlyExitException();
1310 const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");
1311 const char *path_separator = "/";
1316 int len = (int)strlen(data_path_dir) - 1;
1317 if (len < 0) len = 0;
1318 path = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))
1319 + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator);
1324 path += path_separator;
1327 if (relativePath[0] == '/' || relativePath[0] == '\\')
1328 path += relativePath.substr(1);
1330 path += relativePath;
1332 FILE* fp = fopen(path.c_str(), "r");
1337 ADD_FAILURE() << " Requested file \"" << path << "\" does not exist.";
1338 throw PerfEarlyExitException();
1343 void TestBase::RunPerfTestBody()
1347 this->PerfTestBody();
1349 catch(PerfSkipTestException&)
1351 metrics.terminationReason = performance_metrics::TERM_SKIP_TEST;
1354 catch(PerfEarlyExitException&)
1356 metrics.terminationReason = performance_metrics::TERM_INTERRUPT;
1357 return;//no additional failure logging
1359 catch(cv::Exception& e)
1361 metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
1363 if (e.code == cv::Error::GpuApiCallError)
1364 cv::cuda::resetDevice();
1366 FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws cv::Exception:\n " << e.what();
1368 catch(std::exception& e)
1370 metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
1371 FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws std::exception:\n " << e.what();
1375 metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
1376 FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws...";
1380 /*****************************************************************************************\
1381 * ::perf::TestBase::_declareHelper
1382 \*****************************************************************************************/
1383 TestBase::_declareHelper& TestBase::_declareHelper::iterations(unsigned int n)
1385 test->times.clear();
1386 test->times.reserve(n);
1387 test->nIters = std::min(n, TestBase::iterationsLimitDefault);
1388 test->currentIter = (unsigned int)-1;
1389 test->metrics.clear();
1393 TestBase::_declareHelper& TestBase::_declareHelper::time(double timeLimitSecs)
1395 test->times.clear();
1396 test->currentIter = (unsigned int)-1;
1397 test->timeLimit = (int64)(timeLimitSecs * cv::getTickFrequency());
1398 test->metrics.clear();
1402 TestBase::_declareHelper& TestBase::_declareHelper::tbb_threads(int n)
1404 cv::setNumThreads(n);
1408 TestBase::_declareHelper& TestBase::_declareHelper::runs(unsigned int runsNumber)
1410 test->runsPerIteration = runsNumber;
1414 TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, int wtype)
1416 if (!test->times.empty()) return *this;
1417 TestBase::declareArray(test->inputData, a1, wtype);
1421 TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype)
1423 if (!test->times.empty()) return *this;
1424 TestBase::declareArray(test->inputData, a1, wtype);
1425 TestBase::declareArray(test->inputData, a2, wtype);
1429 TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype)
1431 if (!test->times.empty()) return *this;
1432 TestBase::declareArray(test->inputData, a1, wtype);
1433 TestBase::declareArray(test->inputData, a2, wtype);
1434 TestBase::declareArray(test->inputData, a3, wtype);
1438 TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype)
1440 if (!test->times.empty()) return *this;
1441 TestBase::declareArray(test->inputData, a1, wtype);
1442 TestBase::declareArray(test->inputData, a2, wtype);
1443 TestBase::declareArray(test->inputData, a3, wtype);
1444 TestBase::declareArray(test->inputData, a4, wtype);
1448 TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, int wtype)
1450 if (!test->times.empty()) return *this;
1451 TestBase::declareArray(test->outputData, a1, wtype);
1455 TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype)
1457 if (!test->times.empty()) return *this;
1458 TestBase::declareArray(test->outputData, a1, wtype);
1459 TestBase::declareArray(test->outputData, a2, wtype);
1463 TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype)
1465 if (!test->times.empty()) return *this;
1466 TestBase::declareArray(test->outputData, a1, wtype);
1467 TestBase::declareArray(test->outputData, a2, wtype);
1468 TestBase::declareArray(test->outputData, a3, wtype);
1472 TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype)
1474 if (!test->times.empty()) return *this;
1475 TestBase::declareArray(test->outputData, a1, wtype);
1476 TestBase::declareArray(test->outputData, a2, wtype);
1477 TestBase::declareArray(test->outputData, a3, wtype);
1478 TestBase::declareArray(test->outputData, a4, wtype);
1482 TestBase::_declareHelper::_declareHelper(TestBase* t) : test(t)
1486 /*****************************************************************************************\
1488 \*****************************************************************************************/
1491 struct KeypointComparator
1493 std::vector<cv::KeyPoint>& pts_;
1494 comparators::KeypointGreater cmp;
1496 KeypointComparator(std::vector<cv::KeyPoint>& pts) : pts_(pts), cmp() {}
1498 bool operator()(int idx1, int idx2) const
1500 return cmp(pts_[idx1], pts_[idx2]);
1503 const KeypointComparator& operator=(const KeypointComparator&); // quiet MSVC
1507 void perf::sort(std::vector<cv::KeyPoint>& pts, cv::InputOutputArray descriptors)
1509 cv::Mat desc = descriptors.getMat();
1511 CV_Assert(pts.size() == (size_t)desc.rows);
1512 cv::AutoBuffer<int> idxs(desc.rows);
1514 for (int i = 0; i < desc.rows; ++i)
1517 std::sort((int*)idxs, (int*)idxs + desc.rows, KeypointComparator(pts));
1519 std::vector<cv::KeyPoint> spts(pts.size());
1520 cv::Mat sdesc(desc.size(), desc.type());
1522 for(int j = 0; j < desc.rows; ++j)
1524 spts[j] = pts[idxs[j]];
1525 cv::Mat row = sdesc.row(j);
1526 desc.row(idxs[j]).copyTo(row);
1533 /*****************************************************************************************\
1535 \*****************************************************************************************/
1536 bool perf::GpuPerf::targetDevice()
1538 return param_impl == "cuda";
1541 /*****************************************************************************************\
1543 \*****************************************************************************************/
1547 void PrintTo(const MatType& t, ::std::ostream* os)
1549 switch( CV_MAT_DEPTH((int)t) )
1551 case CV_8U: *os << "8U"; break;
1552 case CV_8S: *os << "8S"; break;
1553 case CV_16U: *os << "16U"; break;
1554 case CV_16S: *os << "16S"; break;
1555 case CV_32S: *os << "32S"; break;
1556 case CV_32F: *os << "32F"; break;
1557 case CV_64F: *os << "64F"; break;
1558 case CV_USRTYPE1: *os << "USRTYPE1"; break;
1559 default: *os << "INVALID_TYPE"; break;
1561 *os << 'C' << CV_MAT_CN((int)t);
1566 /*****************************************************************************************\
1568 \*****************************************************************************************/
1571 void PrintTo(const Size& sz, ::std::ostream* os)
1573 *os << /*"Size:" << */sz.width << "x" << sz.height;