1 #include "precomp.hpp"
2
3 #include <map>
4 #include <iostream>
5 #include <fstream>
6
7 #if defined WIN32 || defined _WIN32 || defined WIN64 || defined _WIN64
8 #ifndef NOMINMAX
9 #define NOMINMAX
10 #endif
11 #include <windows.h>
12 #endif
13
14 #ifdef HAVE_CUDA
15 #include "opencv2/core/cuda.hpp"
16 #endif
17
18 #ifdef ANDROID
19 # include <sys/time.h>
20 #endif
21
22 using namespace perf;
23
24 int64 TestBase::timeLimitDefault = 0;
25 unsigned int TestBase::iterationsLimitDefault = (unsigned int)(-1);
26 int64 TestBase::_timeadjustment = 0;
27
28 // Item [0] will be considered the default implementation.
29 static std::vector<std::string> available_impls;
30
31 static std::string param_impl;
32
33 static enum PERF_STRATEGY strategyForce = PERF_STRATEGY_DEFAULT;
34 static enum PERF_STRATEGY strategyModule = PERF_STRATEGY_SIMPLE;
35
36 static double param_max_outliers;
37 static double param_max_deviation;
38 static unsigned int param_min_samples;
39 static unsigned int param_force_samples;
40 static uint64 param_seed;
41 static double param_time_limit;
42 static int param_threads;
43 static bool param_write_sanity;
44 static bool param_verify_sanity;
45 #ifdef CV_COLLECT_IMPL_DATA
46 static bool param_collect_impl;
47 #endif
48 extern bool test_ipp_check;
49
50 #ifdef HAVE_CUDA
51 static int param_cuda_device;
52 #endif
53
54 #ifdef ANDROID
55 static int param_affinity_mask;
56 static bool log_power_checkpoints;
57
58 #include <sys/syscall.h>
59 #include <pthread.h>
setCurrentThreadAffinityMask(int mask)60 static void setCurrentThreadAffinityMask(int mask)
61 {
62 pid_t pid=gettid();
63 int syscallres=syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask);
64 if (syscallres)
65 {
66 int err=errno;
67 err=err;//to avoid warnings about unused variables
68 LOGE("Error in the syscall setaffinity: mask=%d=0x%x err=%d=0x%x", mask, mask, err, err);
69 }
70 }
71 #endif
72
73 static double perf_stability_criteria = 0.03; // 3%
74
75 namespace {
76
77 class PerfEnvironment: public ::testing::Environment
78 {
79 public:
TearDown()80 void TearDown()
81 {
82 cv::setNumThreads(-1);
83 }
84 };
85
86 } // namespace
87
randu(cv::Mat & m)88 static void randu(cv::Mat& m)
89 {
90 const int bigValue = 0x00000FFF;
91 if (m.depth() < CV_32F)
92 {
93 int minmax[] = {0, 256};
94 cv::Mat mr = cv::Mat(m.rows, (int)(m.cols * m.elemSize()), CV_8U, m.ptr(), m.step[0]);
95 cv::randu(mr, cv::Mat(1, 1, CV_32S, minmax), cv::Mat(1, 1, CV_32S, minmax + 1));
96 }
97 else if (m.depth() == CV_32F)
98 {
99 //float minmax[] = {-FLT_MAX, FLT_MAX};
100 float minmax[] = {-bigValue, bigValue};
101 cv::Mat mr = m.reshape(1);
102 cv::randu(mr, cv::Mat(1, 1, CV_32F, minmax), cv::Mat(1, 1, CV_32F, minmax + 1));
103 }
104 else
105 {
106 //double minmax[] = {-DBL_MAX, DBL_MAX};
107 double minmax[] = {-bigValue, bigValue};
108 cv::Mat mr = m.reshape(1);
109 cv::randu(mr, cv::Mat(1, 1, CV_64F, minmax), cv::Mat(1, 1, CV_64F, minmax + 1));
110 }
111 }
112
113 /*****************************************************************************************\
114 * inner exception class for early termination
115 \*****************************************************************************************/
116
117 class PerfEarlyExitException: public cv::Exception {};
118
119 /*****************************************************************************************\
120 * ::perf::Regression
121 \*****************************************************************************************/
122
instance()123 Regression& Regression::instance()
124 {
125 static Regression single;
126 return single;
127 }
128
add(TestBase * test,const std::string & name,cv::InputArray array,double eps,ERROR_TYPE err)129 Regression& Regression::add(TestBase* test, const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)
130 {
131 if(test) test->setVerified();
132 return instance()(name, array, eps, err);
133 }
134
addMoments(TestBase * test,const std::string & name,const cv::Moments & array,double eps,ERROR_TYPE err)135 Regression& Regression::addMoments(TestBase* test, const std::string& name, const cv::Moments& array, double eps, ERROR_TYPE err)
136 {
137 int len = (int)sizeof(cv::Moments) / sizeof(double);
138 cv::Mat m(1, len, CV_64F, (void*)&array);
139
140 return Regression::add(test, name, m, eps, err);
141 }
142
addKeypoints(TestBase * test,const std::string & name,const std::vector<cv::KeyPoint> & array,double eps,ERROR_TYPE err)143 Regression& Regression::addKeypoints(TestBase* test, const std::string& name, const std::vector<cv::KeyPoint>& array, double eps, ERROR_TYPE err)
144 {
145 int len = (int)array.size();
146 cv::Mat pt (len, 1, CV_32FC2, len ? (void*)&array[0].pt : 0, sizeof(cv::KeyPoint));
147 cv::Mat size (len, 1, CV_32FC1, len ? (void*)&array[0].size : 0, sizeof(cv::KeyPoint));
148 cv::Mat angle (len, 1, CV_32FC1, len ? (void*)&array[0].angle : 0, sizeof(cv::KeyPoint));
149 cv::Mat response(len, 1, CV_32FC1, len ? (void*)&array[0].response : 0, sizeof(cv::KeyPoint));
150 cv::Mat octave (len, 1, CV_32SC1, len ? (void*)&array[0].octave : 0, sizeof(cv::KeyPoint));
151 cv::Mat class_id(len, 1, CV_32SC1, len ? (void*)&array[0].class_id : 0, sizeof(cv::KeyPoint));
152
153 return Regression::add(test, name + "-pt", pt, eps, ERROR_ABSOLUTE)
154 (name + "-size", size, eps, ERROR_ABSOLUTE)
155 (name + "-angle", angle, eps, ERROR_ABSOLUTE)
156 (name + "-response", response, eps, err)
157 (name + "-octave", octave, eps, ERROR_ABSOLUTE)
158 (name + "-class_id", class_id, eps, ERROR_ABSOLUTE);
159 }
160
addMatches(TestBase * test,const std::string & name,const std::vector<cv::DMatch> & array,double eps,ERROR_TYPE err)161 Regression& Regression::addMatches(TestBase* test, const std::string& name, const std::vector<cv::DMatch>& array, double eps, ERROR_TYPE err)
162 {
163 int len = (int)array.size();
164 cv::Mat queryIdx(len, 1, CV_32SC1, len ? (void*)&array[0].queryIdx : 0, sizeof(cv::DMatch));
165 cv::Mat trainIdx(len, 1, CV_32SC1, len ? (void*)&array[0].trainIdx : 0, sizeof(cv::DMatch));
166 cv::Mat imgIdx (len, 1, CV_32SC1, len ? (void*)&array[0].imgIdx : 0, sizeof(cv::DMatch));
167 cv::Mat distance(len, 1, CV_32FC1, len ? (void*)&array[0].distance : 0, sizeof(cv::DMatch));
168
169 return Regression::add(test, name + "-queryIdx", queryIdx, DBL_EPSILON, ERROR_ABSOLUTE)
170 (name + "-trainIdx", trainIdx, DBL_EPSILON, ERROR_ABSOLUTE)
171 (name + "-imgIdx", imgIdx, DBL_EPSILON, ERROR_ABSOLUTE)
172 (name + "-distance", distance, eps, err);
173 }
174
Init(const std::string & testSuitName,const std::string & ext)175 void Regression::Init(const std::string& testSuitName, const std::string& ext)
176 {
177 instance().init(testSuitName, ext);
178 }
179
init(const std::string & testSuitName,const std::string & ext)180 void Regression::init(const std::string& testSuitName, const std::string& ext)
181 {
182 if (!storageInPath.empty())
183 {
184 LOGE("Subsequent initialization of Regression utility is not allowed.");
185 return;
186 }
187
188 const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");
189 const char *path_separator = "/";
190
191 if (data_path_dir)
192 {
193 int len = (int)strlen(data_path_dir)-1;
194 if (len < 0) len = 0;
195 std::string path_base = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))
196 + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator)
197 + "perf"
198 + path_separator;
199
200 storageInPath = path_base + testSuitName + ext;
201 storageOutPath = path_base + testSuitName;
202 }
203 else
204 {
205 storageInPath = testSuitName + ext;
206 storageOutPath = testSuitName;
207 }
208
209 suiteName = testSuitName;
210
211 try
212 {
213 if (storageIn.open(storageInPath, cv::FileStorage::READ))
214 {
215 rootIn = storageIn.root();
216 if (storageInPath.length() > 3 && storageInPath.substr(storageInPath.length()-3) == ".gz")
217 storageOutPath += "_new";
218 storageOutPath += ext;
219 }
220 }
221 catch(cv::Exception&)
222 {
223 LOGE("Failed to open sanity data for reading: %s", storageInPath.c_str());
224 }
225
226 if(!storageIn.isOpened())
227 storageOutPath = storageInPath;
228 }
229
Regression()230 Regression::Regression() : regRNG(cv::getTickCount())//this rng should be really random
231 {
232 }
233
~Regression()234 Regression::~Regression()
235 {
236 if (storageIn.isOpened())
237 storageIn.release();
238 if (storageOut.isOpened())
239 {
240 if (!currentTestNodeName.empty())
241 storageOut << "}";
242 storageOut.release();
243 }
244 }
245
write()246 cv::FileStorage& Regression::write()
247 {
248 if (!storageOut.isOpened() && !storageOutPath.empty())
249 {
250 int mode = (storageIn.isOpened() && storageInPath == storageOutPath)
251 ? cv::FileStorage::APPEND : cv::FileStorage::WRITE;
252 storageOut.open(storageOutPath, mode);
253 if (!storageOut.isOpened())
254 {
255 LOGE("Could not open \"%s\" file for writing", storageOutPath.c_str());
256 storageOutPath.clear();
257 }
258 else if (mode == cv::FileStorage::WRITE && !rootIn.empty())
259 {
260 //TODO: write content of rootIn node into the storageOut
261 }
262 }
263 return storageOut;
264 }
265
getCurrentTestNodeName()266 std::string Regression::getCurrentTestNodeName()
267 {
268 const ::testing::TestInfo* const test_info =
269 ::testing::UnitTest::GetInstance()->current_test_info();
270
271 if (test_info == 0)
272 return "undefined";
273
274 std::string nodename = std::string(test_info->test_case_name()) + "--" + test_info->name();
275 size_t idx = nodename.find_first_of('/');
276 if (idx != std::string::npos)
277 nodename.erase(idx);
278
279 const char* type_param = test_info->type_param();
280 if (type_param != 0)
281 (nodename += "--") += type_param;
282
283 const char* value_param = test_info->value_param();
284 if (value_param != 0)
285 (nodename += "--") += value_param;
286
287 for(size_t i = 0; i < nodename.length(); ++i)
288 if (!isalnum(nodename[i]) && '_' != nodename[i])
289 nodename[i] = '-';
290
291 return nodename;
292 }
293
isVector(cv::InputArray a)294 bool Regression::isVector(cv::InputArray a)
295 {
296 return a.kind() == cv::_InputArray::STD_VECTOR_MAT || a.kind() == cv::_InputArray::STD_VECTOR_VECTOR ||
297 a.kind() == cv::_InputArray::STD_VECTOR_UMAT;
298 }
299
getElem(cv::Mat & m,int y,int x,int cn)300 double Regression::getElem(cv::Mat& m, int y, int x, int cn)
301 {
302 switch (m.depth())
303 {
304 case CV_8U: return *(m.ptr<unsigned char>(y, x) + cn);
305 case CV_8S: return *(m.ptr<signed char>(y, x) + cn);
306 case CV_16U: return *(m.ptr<unsigned short>(y, x) + cn);
307 case CV_16S: return *(m.ptr<signed short>(y, x) + cn);
308 case CV_32S: return *(m.ptr<signed int>(y, x) + cn);
309 case CV_32F: return *(m.ptr<float>(y, x) + cn);
310 case CV_64F: return *(m.ptr<double>(y, x) + cn);
311 default: return 0;
312 }
313 }
314
write(cv::Mat m)315 void Regression::write(cv::Mat m)
316 {
317 if (!m.empty() && m.dims < 2) return;
318
319 double min, max;
320 cv::minMaxIdx(m, &min, &max);
321 write() << "min" << min << "max" << max;
322
323 write() << "last" << "{" << "x" << m.size.p[1] - 1 << "y" << m.size.p[0] - 1
324 << "val" << getElem(m, m.size.p[0] - 1, m.size.p[1] - 1, m.channels() - 1) << "}";
325
326 int x, y, cn;
327 x = regRNG.uniform(0, m.size.p[1]);
328 y = regRNG.uniform(0, m.size.p[0]);
329 cn = regRNG.uniform(0, m.channels());
330 write() << "rng1" << "{" << "x" << x << "y" << y;
331 if(cn > 0) write() << "cn" << cn;
332 write() << "val" << getElem(m, y, x, cn) << "}";
333
334 x = regRNG.uniform(0, m.size.p[1]);
335 y = regRNG.uniform(0, m.size.p[0]);
336 cn = regRNG.uniform(0, m.channels());
337 write() << "rng2" << "{" << "x" << x << "y" << y;
338 if (cn > 0) write() << "cn" << cn;
339 write() << "val" << getElem(m, y, x, cn) << "}";
340 }
341
verify(cv::FileNode node,cv::Mat actual,double eps,std::string argname,ERROR_TYPE err)342 void Regression::verify(cv::FileNode node, cv::Mat actual, double eps, std::string argname, ERROR_TYPE err)
343 {
344 if (!actual.empty() && actual.dims < 2) return;
345
346 double expect_min = (double)node["min"];
347 double expect_max = (double)node["max"];
348
349 if (err == ERROR_RELATIVE)
350 eps *= std::max(std::abs(expect_min), std::abs(expect_max));
351
352 double actual_min, actual_max;
353 cv::minMaxIdx(actual, &actual_min, &actual_max);
354
355 ASSERT_NEAR(expect_min, actual_min, eps)
356 << argname << " has unexpected minimal value" << std::endl;
357 ASSERT_NEAR(expect_max, actual_max, eps)
358 << argname << " has unexpected maximal value" << std::endl;
359
360 cv::FileNode last = node["last"];
361 double actual_last = getElem(actual, actual.size.p[0] - 1, actual.size.p[1] - 1, actual.channels() - 1);
362 int expect_cols = (int)last["x"] + 1;
363 int expect_rows = (int)last["y"] + 1;
364 ASSERT_EQ(expect_cols, actual.size.p[1])
365 << argname << " has unexpected number of columns" << std::endl;
366 ASSERT_EQ(expect_rows, actual.size.p[0])
367 << argname << " has unexpected number of rows" << std::endl;
368
369 double expect_last = (double)last["val"];
370 ASSERT_NEAR(expect_last, actual_last, eps)
371 << argname << " has unexpected value of the last element" << std::endl;
372
373 cv::FileNode rng1 = node["rng1"];
374 int x1 = rng1["x"];
375 int y1 = rng1["y"];
376 int cn1 = rng1["cn"];
377
378 double expect_rng1 = (double)rng1["val"];
379 // it is safe to use x1 and y1 without checks here because we have already
380 // verified that mat size is the same as recorded
381 double actual_rng1 = getElem(actual, y1, x1, cn1);
382
383 ASSERT_NEAR(expect_rng1, actual_rng1, eps)
384 << argname << " has unexpected value of the ["<< x1 << ":" << y1 << ":" << cn1 <<"] element" << std::endl;
385
386 cv::FileNode rng2 = node["rng2"];
387 int x2 = rng2["x"];
388 int y2 = rng2["y"];
389 int cn2 = rng2["cn"];
390
391 double expect_rng2 = (double)rng2["val"];
392 double actual_rng2 = getElem(actual, y2, x2, cn2);
393
394 ASSERT_NEAR(expect_rng2, actual_rng2, eps)
395 << argname << " has unexpected value of the ["<< x2 << ":" << y2 << ":" << cn2 <<"] element" << std::endl;
396 }
397
write(cv::InputArray array)398 void Regression::write(cv::InputArray array)
399 {
400 write() << "kind" << array.kind();
401 write() << "type" << array.type();
402 if (isVector(array))
403 {
404 int total = (int)array.total();
405 int idx = regRNG.uniform(0, total);
406 write() << "len" << total;
407 write() << "idx" << idx;
408
409 cv::Mat m = array.getMat(idx);
410
411 if (m.total() * m.channels() < 26) //5x5 or smaller
412 write() << "val" << m;
413 else
414 write(m);
415 }
416 else
417 {
418 if (array.total() * array.channels() < 26) //5x5 or smaller
419 write() << "val" << array.getMat();
420 else
421 write(array.getMat());
422 }
423 }
424
countViolations(const cv::Mat & expected,const cv::Mat & actual,const cv::Mat & diff,double eps,double * max_violation=0,double * max_allowed=0)425 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)
426 {
427 cv::Mat diff64f;
428 diff.reshape(1).convertTo(diff64f, CV_64F);
429
430 cv::Mat expected_abs = cv::abs(expected.reshape(1));
431 cv::Mat actual_abs = cv::abs(actual.reshape(1));
432 cv::Mat maximum, mask;
433 cv::max(expected_abs, actual_abs, maximum);
434 cv::multiply(maximum, cv::Vec<double, 1>(eps), maximum, CV_64F);
435 cv::compare(diff64f, maximum, mask, cv::CMP_GT);
436
437 int v = cv::countNonZero(mask);
438
439 if (v > 0 && max_violation != 0 && max_allowed != 0)
440 {
441 int loc[10] = {0};
442 cv::minMaxIdx(maximum, 0, max_allowed, 0, loc, mask);
443 *max_violation = diff64f.at<double>(loc[0], loc[1]);
444 }
445
446 return v;
447 }
448
verify(cv::FileNode node,cv::InputArray array,double eps,ERROR_TYPE err)449 void Regression::verify(cv::FileNode node, cv::InputArray array, double eps, ERROR_TYPE err)
450 {
451 int expected_kind = (int)node["kind"];
452 int expected_type = (int)node["type"];
453 ASSERT_EQ(expected_kind, array.kind()) << " Argument \"" << node.name() << "\" has unexpected kind";
454 ASSERT_EQ(expected_type, array.type()) << " Argument \"" << node.name() << "\" has unexpected type";
455
456 cv::FileNode valnode = node["val"];
457 if (isVector(array))
458 {
459 int expected_length = (int)node["len"];
460 ASSERT_EQ(expected_length, (int)array.total()) << " Vector \"" << node.name() << "\" has unexpected length";
461 int idx = node["idx"];
462
463 cv::Mat actual = array.getMat(idx);
464
465 if (valnode.isNone())
466 {
467 ASSERT_LE((size_t)26, actual.total() * (size_t)actual.channels())
468 << " \"" << node.name() << "[" << idx << "]\" has unexpected number of elements";
469 verify(node, actual, eps, cv::format("%s[%d]", node.name().c_str(), idx), err);
470 }
471 else
472 {
473 cv::Mat expected;
474 valnode >> expected;
475
476 if(expected.empty())
477 {
478 ASSERT_TRUE(actual.empty())
479 << " expected empty " << node.name() << "[" << idx<< "]";
480 }
481 else
482 {
483 ASSERT_EQ(expected.size(), actual.size())
484 << " " << node.name() << "[" << idx<< "] has unexpected size";
485
486 cv::Mat diff;
487 cv::absdiff(expected, actual, diff);
488
489 if (err == ERROR_ABSOLUTE)
490 {
491 if (!cv::checkRange(diff, true, 0, 0, eps))
492 {
493 if(expected.total() * expected.channels() < 12)
494 std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
495
496 double max;
497 cv::minMaxIdx(diff.reshape(1), 0, &max);
498
499 FAIL() << " Absolute difference (=" << max << ") between argument \""
500 << node.name() << "[" << idx << "]\" and expected value is greater than " << eps;
501 }
502 }
503 else if (err == ERROR_RELATIVE)
504 {
505 double maxv, maxa;
506 int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);
507 if (violations > 0)
508 {
509 if(expected.total() * expected.channels() < 12)
510 std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
511
512 FAIL() << " Relative difference (" << maxv << " of " << maxa << " allowed) between argument \""
513 << node.name() << "[" << idx << "]\" and expected value is greater than " << eps << " in " << violations << " points";
514 }
515 }
516 }
517 }
518 }
519 else
520 {
521 if (valnode.isNone())
522 {
523 ASSERT_LE((size_t)26, array.total() * (size_t)array.channels())
524 << " Argument \"" << node.name() << "\" has unexpected number of elements";
525 verify(node, array.getMat(), eps, "Argument \"" + node.name() + "\"", err);
526 }
527 else
528 {
529 cv::Mat expected;
530 valnode >> expected;
531 cv::Mat actual = array.getMat();
532
533 if(expected.empty())
534 {
535 ASSERT_TRUE(actual.empty())
536 << " expected empty " << node.name();
537 }
538 else
539 {
540 ASSERT_EQ(expected.size(), actual.size())
541 << " Argument \"" << node.name() << "\" has unexpected size";
542
543 cv::Mat diff;
544 cv::absdiff(expected, actual, diff);
545
546 if (err == ERROR_ABSOLUTE)
547 {
548 if (!cv::checkRange(diff, true, 0, 0, eps))
549 {
550 if(expected.total() * expected.channels() < 12)
551 std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
552
553 double max;
554 cv::minMaxIdx(diff.reshape(1), 0, &max);
555
556 FAIL() << " Difference (=" << max << ") between argument1 \"" << node.name()
557 << "\" and expected value is greater than " << eps;
558 }
559 }
560 else if (err == ERROR_RELATIVE)
561 {
562 double maxv, maxa;
563 int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);
564 if (violations > 0)
565 {
566 if(expected.total() * expected.channels() < 12)
567 std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
568
569 FAIL() << " Relative difference (" << maxv << " of " << maxa << " allowed) between argument \"" << node.name()
570 << "\" and expected value is greater than " << eps << " in " << violations << " points";
571 }
572 }
573 }
574 }
575 }
576 }
577
operator ()(const std::string & name,cv::InputArray array,double eps,ERROR_TYPE err)578 Regression& Regression::operator() (const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)
579 {
580 // exit if current test is already failed
581 if(::testing::UnitTest::GetInstance()->current_test_info()->result()->Failed()) return *this;
582
583 if(!array.empty() && array.depth() == CV_USRTYPE1)
584 {
585 ADD_FAILURE() << " Can not check regression for CV_USRTYPE1 data type for " << name;
586 return *this;
587 }
588
589 std::string nodename = getCurrentTestNodeName();
590
591 cv::FileNode n = rootIn[nodename];
592 if(n.isNone())
593 {
594 if(param_write_sanity)
595 {
596 if (nodename != currentTestNodeName)
597 {
598 if (!currentTestNodeName.empty())
599 write() << "}";
600 currentTestNodeName = nodename;
601
602 write() << nodename << "{";
603 }
604 // TODO: verify that name is alphanumeric, current error message is useless
605 write() << name << "{";
606 write(array);
607 write() << "}";
608 }
609 else if(param_verify_sanity)
610 {
611 ADD_FAILURE() << " No regression data for " << name << " argument";
612 }
613 }
614 else
615 {
616 cv::FileNode this_arg = n[name];
617 if (!this_arg.isMap())
618 ADD_FAILURE() << " No regression data for " << name << " argument";
619 else
620 verify(this_arg, array, eps, err);
621 }
622
623 return *this;
624 }
625
626
627 /*****************************************************************************************\
628 * ::perf::performance_metrics
629 \*****************************************************************************************/
performance_metrics()630 performance_metrics::performance_metrics()
631 {
632 clear();
633 }
634
clear()635 void performance_metrics::clear()
636 {
637 bytesIn = 0;
638 bytesOut = 0;
639 samples = 0;
640 outliers = 0;
641 gmean = 0;
642 gstddev = 0;
643 mean = 0;
644 stddev = 0;
645 median = 0;
646 min = 0;
647 frequency = 0;
648 terminationReason = TERM_UNKNOWN;
649 }
650
651 /*****************************************************************************************\
652 * Performance validation results
653 \*****************************************************************************************/
654
655 static bool perf_validation_enabled = false;
656
657 static std::string perf_validation_results_directory;
658 static std::map<std::string, float> perf_validation_results;
659 static std::string perf_validation_results_outfile;
660
661 static double perf_validation_criteria = 0.03; // 3 %
662 static double perf_validation_time_threshold_ms = 0.1;
663 static int perf_validation_idle_delay_ms = 3000; // 3 sec
664
loadPerfValidationResults(const std::string & fileName)665 static void loadPerfValidationResults(const std::string& fileName)
666 {
667 perf_validation_results.clear();
668 std::ifstream infile(fileName.c_str());
669 while (!infile.eof())
670 {
671 std::string name;
672 float value = 0;
673 if (!(infile >> value))
674 {
675 if (infile.eof())
676 break; // it is OK
677 std::cout << "ERROR: Can't load performance validation results from " << fileName << "!" << std::endl;
678 return;
679 }
680 infile.ignore(1);
681 if (!(std::getline(infile, name)))
682 {
683 std::cout << "ERROR: Can't load performance validation results from " << fileName << "!" << std::endl;
684 return;
685 }
686 if (!name.empty() && name[name.size() - 1] == '\r') // CRLF processing on Linux
687 name.resize(name.size() - 1);
688 perf_validation_results[name] = value;
689 }
690 std::cout << "Performance validation results loaded from " << fileName << " (" << perf_validation_results.size() << " entries)" << std::endl;
691 }
692
savePerfValidationResult(const std::string & name,float value)693 static void savePerfValidationResult(const std::string& name, float value)
694 {
695 perf_validation_results[name] = value;
696 }
697
savePerfValidationResults()698 static void savePerfValidationResults()
699 {
700 if (!perf_validation_results_outfile.empty())
701 {
702 std::ofstream outfile((perf_validation_results_directory + perf_validation_results_outfile).c_str());
703 std::map<std::string, float>::const_iterator i;
704 for (i = perf_validation_results.begin(); i != perf_validation_results.end(); ++i)
705 {
706 outfile << i->second << ';';
707 outfile << i->first << std::endl;
708 }
709 outfile.close();
710 std::cout << "Performance validation results saved (" << perf_validation_results.size() << " entries)" << std::endl;
711 }
712 }
713
714 class PerfValidationEnvironment : public ::testing::Environment
715 {
716 public:
~PerfValidationEnvironment()717 virtual ~PerfValidationEnvironment() {}
SetUp()718 virtual void SetUp() {}
719
TearDown()720 virtual void TearDown()
721 {
722 savePerfValidationResults();
723 }
724 };
725
726
727
728 /*****************************************************************************************\
729 * ::perf::TestBase
730 \*****************************************************************************************/
731
732
Init(int argc,const char * const argv[])733 void TestBase::Init(int argc, const char* const argv[])
734 {
735 std::vector<std::string> plain_only;
736 plain_only.push_back("plain");
737 TestBase::Init(plain_only, argc, argv);
738 }
739
Init(const std::vector<std::string> & availableImpls,int argc,const char * const argv[])740 void TestBase::Init(const std::vector<std::string> & availableImpls,
741 int argc, const char* const argv[])
742 {
743 available_impls = availableImpls;
744
745 const std::string command_line_keys =
746 "{ perf_max_outliers |8 |percent of allowed outliers}"
747 "{ perf_min_samples |10 |minimal required numer of samples}"
748 "{ perf_force_samples |100 |force set maximum number of samples for all tests}"
749 "{ perf_seed |809564 |seed for random numbers generator}"
750 "{ perf_threads |-1 |the number of worker threads, if parallel execution is enabled}"
751 "{ perf_write_sanity |false |create new records for sanity checks}"
752 "{ perf_verify_sanity |false |fail tests having no regression data for sanity checks}"
753 "{ perf_impl |" + available_impls[0] +
754 "|the implementation variant of functions under test}"
755 "{ perf_list_impls |false |list available implementation variants and exit}"
756 "{ perf_run_cpu |false |deprecated, equivalent to --perf_impl=plain}"
757 "{ perf_strategy |default |specifies performance measuring strategy: default, base or simple (weak restrictions)}"
758 "{ perf_read_validation_results | |specifies file name with performance results from previous run}"
759 "{ perf_write_validation_results | |specifies file name to write performance validation results}"
760 #ifdef ANDROID
761 "{ perf_time_limit |6.0 |default time limit for a single test (in seconds)}"
762 "{ perf_affinity_mask |0 |set affinity mask for the main thread}"
763 "{ perf_log_power_checkpoints | |additional xml logging for power measurement}"
764 #else
765 "{ perf_time_limit |3.0 |default time limit for a single test (in seconds)}"
766 #endif
767 "{ perf_max_deviation |1.0 |}"
768 #ifdef HAVE_IPP
769 "{ perf_ipp_check |false |check whether IPP works without failures}"
770 #endif
771 #ifdef CV_COLLECT_IMPL_DATA
772 "{ perf_collect_impl |false |collect info about executed implementations}"
773 #endif
774 "{ help h |false |print help info}"
775 #ifdef HAVE_CUDA
776 "{ perf_cuda_device |0 |run CUDA test suite onto specific CUDA capable device}"
777 "{ perf_cuda_info_only |false |print an information about system and an available CUDA devices and then exit.}"
778 #endif
779 ;
780
781 cv::CommandLineParser args(argc, argv, command_line_keys);
782 if (args.has("help"))
783 {
784 args.printMessage();
785 return;
786 }
787
788 ::testing::AddGlobalTestEnvironment(new PerfEnvironment);
789
790 param_impl = args.has("perf_run_cpu") ? "plain" : args.get<std::string>("perf_impl");
791 std::string perf_strategy = args.get<std::string>("perf_strategy");
792 if (perf_strategy == "default")
793 {
794 // nothing
795 }
796 else if (perf_strategy == "base")
797 {
798 strategyForce = PERF_STRATEGY_BASE;
799 }
800 else if (perf_strategy == "simple")
801 {
802 strategyForce = PERF_STRATEGY_SIMPLE;
803 }
804 else
805 {
806 printf("No such strategy: %s\n", perf_strategy.c_str());
807 exit(1);
808 }
809 param_max_outliers = std::min(100., std::max(0., args.get<double>("perf_max_outliers")));
810 param_min_samples = std::max(1u, args.get<unsigned int>("perf_min_samples"));
811 param_max_deviation = std::max(0., args.get<double>("perf_max_deviation"));
812 param_seed = args.get<unsigned int>("perf_seed");
813 param_time_limit = std::max(0., args.get<double>("perf_time_limit"));
814 param_force_samples = args.get<unsigned int>("perf_force_samples");
815 param_write_sanity = args.has("perf_write_sanity");
816 param_verify_sanity = args.has("perf_verify_sanity");
817 test_ipp_check = !args.has("perf_ipp_check") ? getenv("OPENCV_IPP_CHECK") != NULL : true;
818 param_threads = args.get<int>("perf_threads");
819 #ifdef CV_COLLECT_IMPL_DATA
820 param_collect_impl = args.has("perf_collect_impl");
821 #endif
822 #ifdef ANDROID
823 param_affinity_mask = args.get<int>("perf_affinity_mask");
824 log_power_checkpoints = args.has("perf_log_power_checkpoints");
825 #endif
826
827 bool param_list_impls = args.has("perf_list_impls");
828
829 if (param_list_impls)
830 {
831 fputs("Available implementation variants:", stdout);
832 for (size_t i = 0; i < available_impls.size(); ++i) {
833 putchar(' ');
834 fputs(available_impls[i].c_str(), stdout);
835 }
836 putchar('\n');
837 exit(0);
838 }
839
840 if (std::find(available_impls.begin(), available_impls.end(), param_impl) == available_impls.end())
841 {
842 printf("No such implementation: %s\n", param_impl.c_str());
843 exit(1);
844 }
845
846 #ifdef CV_COLLECT_IMPL_DATA
847 if(param_collect_impl)
848 cv::setUseCollection(1);
849 else
850 cv::setUseCollection(0);
851 #endif
852
853 #ifdef HAVE_CUDA
854
855 bool printOnly = args.has("perf_cuda_info_only");
856
857 if (printOnly)
858 exit(0);
859 #endif
860
861 if (available_impls.size() > 1)
862 printf("[----------]\n[ INFO ] \tImplementation variant: %s.\n[----------]\n", param_impl.c_str()), fflush(stdout);
863
864 #ifdef HAVE_CUDA
865
866 param_cuda_device = std::max(0, std::min(cv::cuda::getCudaEnabledDeviceCount(), args.get<int>("perf_cuda_device")));
867
868 if (param_impl == "cuda")
869 {
870 cv::cuda::DeviceInfo info(param_cuda_device);
871 if (!info.isCompatible())
872 {
873 printf("[----------]\n[ FAILURE ] \tDevice %s is NOT compatible with current CUDA module build.\n[----------]\n", info.name()), fflush(stdout);
874 exit(-1);
875 }
876
877 cv::cuda::setDevice(param_cuda_device);
878
879 printf("[----------]\n[ GPU INFO ] \tRun test suite on %s GPU.\n[----------]\n", info.name()), fflush(stdout);
880 }
881 #endif
882
883 {
884 const char* path = getenv("OPENCV_PERF_VALIDATION_DIR");
885 if (path)
886 perf_validation_results_directory = path;
887 }
888
889 std::string fileName_perf_validation_results_src = args.get<std::string>("perf_read_validation_results");
890 if (!fileName_perf_validation_results_src.empty())
891 {
892 perf_validation_enabled = true;
893 loadPerfValidationResults(perf_validation_results_directory + fileName_perf_validation_results_src);
894 }
895
896 perf_validation_results_outfile = args.get<std::string>("perf_write_validation_results");
897 if (!perf_validation_results_outfile.empty())
898 {
899 perf_validation_enabled = true;
900 ::testing::AddGlobalTestEnvironment(new PerfValidationEnvironment());
901 }
902
903 if (!args.check())
904 {
905 args.printErrors();
906 return;
907 }
908
909 timeLimitDefault = param_time_limit == 0.0 ? 1 : (int64)(param_time_limit * cv::getTickFrequency());
910 iterationsLimitDefault = param_force_samples == 0 ? (unsigned)(-1) : param_force_samples;
911 _timeadjustment = _calibrate();
912 }
913
RecordRunParameters()914 void TestBase::RecordRunParameters()
915 {
916 ::testing::Test::RecordProperty("cv_implementation", param_impl);
917 ::testing::Test::RecordProperty("cv_num_threads", param_threads);
918
919 #ifdef HAVE_CUDA
920 if (param_impl == "cuda")
921 {
922 cv::cuda::DeviceInfo info(param_cuda_device);
923 ::testing::Test::RecordProperty("cv_cuda_gpu", info.name());
924 }
925 #endif
926 }
927
getSelectedImpl()928 std::string TestBase::getSelectedImpl()
929 {
930 return param_impl;
931 }
932
setModulePerformanceStrategy(enum PERF_STRATEGY strategy)933 enum PERF_STRATEGY TestBase::setModulePerformanceStrategy(enum PERF_STRATEGY strategy)
934 {
935 enum PERF_STRATEGY ret = strategyModule;
936 strategyModule = strategy;
937 return ret;
938 }
939
getCurrentModulePerformanceStrategy()940 enum PERF_STRATEGY TestBase::getCurrentModulePerformanceStrategy()
941 {
942 return strategyForce == PERF_STRATEGY_DEFAULT ? strategyModule : strategyForce;
943 }
944
945
_calibrate()946 int64 TestBase::_calibrate()
947 {
948 class _helper : public ::perf::TestBase
949 {
950 public:
951 performance_metrics& getMetrics() { return calcMetrics(); }
952 virtual void TestBody() {}
953 virtual void PerfTestBody()
954 {
955 //the whole system warmup
956 SetUp();
957 cv::Mat a(2048, 2048, CV_32S, cv::Scalar(1));
958 cv::Mat b(2048, 2048, CV_32S, cv::Scalar(2));
959 declare.time(30);
960 double s = 0;
961 for(declare.iterations(20); startTimer(), next(); stopTimer())
962 s+=a.dot(b);
963 declare.time(s);
964
965 //self calibration
966 SetUp();
967 for(declare.iterations(1000); startTimer(), next(); stopTimer()){}
968 }
969 };
970
971 _timeadjustment = 0;
972 _helper h;
973 h.PerfTestBody();
974 double compensation = h.getMetrics().min;
975 if (getCurrentModulePerformanceStrategy() == PERF_STRATEGY_SIMPLE)
976 {
977 CV_Assert(compensation < 0.01 * cv::getTickFrequency());
978 compensation = 0.0f; // simple strategy doesn't require any compensation
979 }
980 LOGD("Time compensation is %.0f", compensation);
981 return (int64)compensation;
982 }
983
984 #ifdef _MSC_VER
985 # pragma warning(push)
986 # pragma warning(disable:4355) // 'this' : used in base member initializer list
987 #endif
TestBase()988 TestBase::TestBase(): testStrategy(PERF_STRATEGY_DEFAULT), declare(this)
989 {
990 lastTime = totalTime = timeLimit = 0;
991 nIters = currentIter = runsPerIteration = 0;
992 minIters = param_min_samples;
993 verified = false;
994 perfValidationStage = 0;
995 }
996 #ifdef _MSC_VER
997 # pragma warning(pop)
998 #endif
999
1000
declareArray(SizeVector & sizes,cv::InputOutputArray a,WarmUpType wtype)1001 void TestBase::declareArray(SizeVector& sizes, cv::InputOutputArray a, WarmUpType wtype)
1002 {
1003 if (!a.empty())
1004 {
1005 sizes.push_back(std::pair<int, cv::Size>(getSizeInBytes(a), getSize(a)));
1006 warmup(a, wtype);
1007 }
1008 else if (a.kind() != cv::_InputArray::NONE)
1009 ADD_FAILURE() << " Uninitialized input/output parameters are not allowed for performance tests";
1010 }
1011
warmup(cv::InputOutputArray a,WarmUpType wtype)1012 void TestBase::warmup(cv::InputOutputArray a, WarmUpType wtype)
1013 {
1014 if (a.empty())
1015 return;
1016 else if (a.isUMat())
1017 {
1018 if (wtype == WARMUP_RNG || wtype == WARMUP_WRITE)
1019 {
1020 int depth = a.depth();
1021 if (depth == CV_8U)
1022 cv::randu(a, 0, 256);
1023 else if (depth == CV_8S)
1024 cv::randu(a, -128, 128);
1025 else if (depth == CV_16U)
1026 cv::randu(a, 0, 1024);
1027 else if (depth == CV_32F || depth == CV_64F)
1028 cv::randu(a, -1.0, 1.0);
1029 else if (depth == CV_16S || depth == CV_32S)
1030 cv::randu(a, -4096, 4096);
1031 else
1032 CV_Error(cv::Error::StsUnsupportedFormat, "Unsupported format");
1033 }
1034 return;
1035 }
1036 else if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
1037 warmup_impl(a.getMat(), wtype);
1038 else
1039 {
1040 size_t total = a.total();
1041 for (size_t i = 0; i < total; ++i)
1042 warmup_impl(a.getMat((int)i), wtype);
1043 }
1044 }
1045
getSizeInBytes(cv::InputArray a)1046 int TestBase::getSizeInBytes(cv::InputArray a)
1047 {
1048 if (a.empty()) return 0;
1049 int total = (int)a.total();
1050 if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
1051 return total * CV_ELEM_SIZE(a.type());
1052
1053 int size = 0;
1054 for (int i = 0; i < total; ++i)
1055 size += (int)a.total(i) * CV_ELEM_SIZE(a.type(i));
1056
1057 return size;
1058 }
1059
getSize(cv::InputArray a)1060 cv::Size TestBase::getSize(cv::InputArray a)
1061 {
1062 if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
1063 return a.size();
1064 return cv::Size();
1065 }
1066
getCurrentPerformanceStrategy() const1067 PERF_STRATEGY TestBase::getCurrentPerformanceStrategy() const
1068 {
1069 if (strategyForce == PERF_STRATEGY_DEFAULT)
1070 return (testStrategy == PERF_STRATEGY_DEFAULT) ? strategyModule : testStrategy;
1071 else
1072 return strategyForce;
1073 }
1074
next()1075 bool TestBase::next()
1076 {
1077 static int64 lastActivityPrintTime = 0;
1078
1079 if (currentIter != (unsigned int)-1)
1080 {
1081 if (currentIter + 1 != times.size())
1082 ADD_FAILURE() << " next() is called before stopTimer()";
1083 }
1084 else
1085 {
1086 lastActivityPrintTime = 0;
1087 metrics.clear();
1088 }
1089
1090 cv::theRNG().state = param_seed; //this rng should generate same numbers for each run
1091 ++currentIter;
1092
1093 bool has_next = false;
1094
1095 do {
1096 assert(currentIter == times.size());
1097 if (currentIter == 0)
1098 {
1099 has_next = true;
1100 break;
1101 }
1102
1103 if (getCurrentPerformanceStrategy() == PERF_STRATEGY_BASE)
1104 {
1105 has_next = currentIter < nIters && totalTime < timeLimit;
1106 }
1107 else
1108 {
1109 assert(getCurrentPerformanceStrategy() == PERF_STRATEGY_SIMPLE);
1110 if (totalTime - lastActivityPrintTime >= cv::getTickFrequency() * 10)
1111 {
1112 std::cout << '.' << std::endl;
1113 lastActivityPrintTime = totalTime;
1114 }
1115 if (currentIter >= nIters)
1116 {
1117 has_next = false;
1118 break;
1119 }
1120 if (currentIter < minIters)
1121 {
1122 has_next = true;
1123 break;
1124 }
1125
1126 calcMetrics();
1127
1128 if (fabs(metrics.mean) > 1e-6)
1129 has_next = metrics.stddev > perf_stability_criteria * fabs(metrics.mean);
1130 else
1131 has_next = true;
1132 }
1133 } while (false);
1134
1135 if (perf_validation_enabled && !has_next)
1136 {
1137 calcMetrics();
1138 double median_ms = metrics.median * 1000.0f / metrics.frequency;
1139
1140 const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
1141 std::string name = (test_info == 0) ? "" :
1142 std::string(test_info->test_case_name()) + "--" + test_info->name();
1143
1144 if (!perf_validation_results.empty() && !name.empty())
1145 {
1146 std::map<std::string, float>::iterator i = perf_validation_results.find(name);
1147 bool isSame = false;
1148 bool found = false;
1149 bool grow = false;
1150 if (i != perf_validation_results.end())
1151 {
1152 found = true;
1153 double prev_result = i->second;
1154 grow = median_ms > prev_result;
1155 isSame = fabs(median_ms - prev_result) <= perf_validation_criteria * fabs(median_ms);
1156 if (!isSame)
1157 {
1158 if (perfValidationStage == 0)
1159 {
1160 printf("Performance is changed (samples = %d, median):\n %.2f ms (current)\n %.2f ms (previous)\n", (int)times.size(), median_ms, prev_result);
1161 }
1162 }
1163 }
1164 else
1165 {
1166 if (perfValidationStage == 0)
1167 printf("New performance result is detected\n");
1168 }
1169 if (!isSame)
1170 {
1171 if (perfValidationStage < 2)
1172 {
1173 if (perfValidationStage == 0 && currentIter <= minIters * 3 && currentIter < nIters)
1174 {
1175 unsigned int new_minIters = std::max(minIters * 5, currentIter * 3);
1176 printf("Increase minIters from %u to %u\n", minIters, new_minIters);
1177 minIters = new_minIters;
1178 has_next = true;
1179 perfValidationStage++;
1180 }
1181 else if (found && currentIter >= nIters &&
1182 median_ms > perf_validation_time_threshold_ms &&
1183 (grow || metrics.stddev > perf_stability_criteria * fabs(metrics.mean)))
1184 {
1185 printf("Performance is unstable, it may be a result of overheat problems\n");
1186 printf("Idle delay for %d ms... \n", perf_validation_idle_delay_ms);
1187 #if defined WIN32 || defined _WIN32 || defined WIN64 || defined _WIN64
1188 Sleep(perf_validation_idle_delay_ms);
1189 #else
1190 usleep(perf_validation_idle_delay_ms * 1000);
1191 #endif
1192 has_next = true;
1193 minIters = std::min(minIters * 5, nIters);
1194 // reset collected samples
1195 currentIter = 0;
1196 times.clear();
1197 metrics.clear();
1198 perfValidationStage += 2;
1199 }
1200 if (!has_next)
1201 {
1202 printf("Assume that current result is valid\n");
1203 }
1204 }
1205 else
1206 {
1207 printf("Re-measured performance result: %.2f ms\n", median_ms);
1208 }
1209 }
1210 }
1211
1212 if (!has_next && !name.empty())
1213 {
1214 savePerfValidationResult(name, (float)median_ms);
1215 }
1216 }
1217
1218 #ifdef ANDROID
1219 if (log_power_checkpoints)
1220 {
1221 timeval tim;
1222 gettimeofday(&tim, NULL);
1223 unsigned long long t1 = tim.tv_sec * 1000LLU + (unsigned long long)(tim.tv_usec / 1000.f);
1224
1225 if (currentIter == 1) RecordProperty("test_start", cv::format("%llu",t1).c_str());
1226 if (!has_next) RecordProperty("test_complete", cv::format("%llu",t1).c_str());
1227 }
1228 #endif
1229
1230 if (has_next)
1231 startTimer(); // really we should measure activity from this moment, so reset start time
1232 return has_next;
1233 }
1234
warmup_impl(cv::Mat m,WarmUpType wtype)1235 void TestBase::warmup_impl(cv::Mat m, WarmUpType wtype)
1236 {
1237 switch(wtype)
1238 {
1239 case WARMUP_READ:
1240 cv::sum(m.reshape(1));
1241 return;
1242 case WARMUP_WRITE:
1243 m.reshape(1).setTo(cv::Scalar::all(0));
1244 return;
1245 case WARMUP_RNG:
1246 randu(m);
1247 return;
1248 default:
1249 return;
1250 }
1251 }
1252
getTotalInputSize() const1253 unsigned int TestBase::getTotalInputSize() const
1254 {
1255 unsigned int res = 0;
1256 for (SizeVector::const_iterator i = inputData.begin(); i != inputData.end(); ++i)
1257 res += i->first;
1258 return res;
1259 }
1260
getTotalOutputSize() const1261 unsigned int TestBase::getTotalOutputSize() const
1262 {
1263 unsigned int res = 0;
1264 for (SizeVector::const_iterator i = outputData.begin(); i != outputData.end(); ++i)
1265 res += i->first;
1266 return res;
1267 }
1268
startTimer()1269 void TestBase::startTimer()
1270 {
1271 lastTime = cv::getTickCount();
1272 }
1273
stopTimer()1274 void TestBase::stopTimer()
1275 {
1276 int64 time = cv::getTickCount();
1277 if (lastTime == 0)
1278 ADD_FAILURE() << " stopTimer() is called before startTimer()/next()";
1279 lastTime = time - lastTime;
1280 totalTime += lastTime;
1281 lastTime -= _timeadjustment;
1282 if (lastTime < 0) lastTime = 0;
1283 times.push_back(lastTime);
1284 lastTime = 0;
1285 }
1286
calcMetrics()1287 performance_metrics& TestBase::calcMetrics()
1288 {
1289 CV_Assert(metrics.samples <= (unsigned int)currentIter);
1290 if ((metrics.samples == (unsigned int)currentIter) || times.size() == 0)
1291 return metrics;
1292
1293 metrics.bytesIn = getTotalInputSize();
1294 metrics.bytesOut = getTotalOutputSize();
1295 metrics.frequency = cv::getTickFrequency();
1296 metrics.samples = (unsigned int)times.size();
1297 metrics.outliers = 0;
1298
1299 if (metrics.terminationReason != performance_metrics::TERM_INTERRUPT && metrics.terminationReason != performance_metrics::TERM_EXCEPTION)
1300 {
1301 if (currentIter == nIters)
1302 metrics.terminationReason = performance_metrics::TERM_ITERATIONS;
1303 else if (totalTime >= timeLimit)
1304 metrics.terminationReason = performance_metrics::TERM_TIME;
1305 else
1306 metrics.terminationReason = performance_metrics::TERM_UNKNOWN;
1307 }
1308
1309 std::sort(times.begin(), times.end());
1310
1311 TimeVector::const_iterator start = times.begin();
1312 TimeVector::const_iterator end = times.end();
1313
1314 if (getCurrentPerformanceStrategy() == PERF_STRATEGY_BASE)
1315 {
1316 //estimate mean and stddev for log(time)
1317 double gmean = 0;
1318 double gstddev = 0;
1319 int n = 0;
1320 for(TimeVector::const_iterator i = times.begin(); i != times.end(); ++i)
1321 {
1322 double x = static_cast<double>(*i)/runsPerIteration;
1323 if (x < DBL_EPSILON) continue;
1324 double lx = log(x);
1325
1326 ++n;
1327 double delta = lx - gmean;
1328 gmean += delta / n;
1329 gstddev += delta * (lx - gmean);
1330 }
1331
1332 gstddev = n > 1 ? sqrt(gstddev / (n - 1)) : 0;
1333
1334 //filter outliers assuming log-normal distribution
1335 //http://stackoverflow.com/questions/1867426/modeling-distribution-of-performance-measurements
1336 if (gstddev > DBL_EPSILON)
1337 {
1338 double minout = exp(gmean - 3 * gstddev) * runsPerIteration;
1339 double maxout = exp(gmean + 3 * gstddev) * runsPerIteration;
1340 while(*start < minout) ++start, ++metrics.outliers;
1341 do --end, ++metrics.outliers; while(*end > maxout);
1342 ++end, --metrics.outliers;
1343 }
1344 }
1345 else if (getCurrentPerformanceStrategy() == PERF_STRATEGY_SIMPLE)
1346 {
1347 metrics.outliers = static_cast<int>(times.size() * param_max_outliers / 100);
1348 for (unsigned int i = 0; i < metrics.outliers; i++)
1349 --end;
1350 }
1351 else
1352 {
1353 assert(false);
1354 }
1355
1356 int offset = static_cast<int>(start - times.begin());
1357
1358 metrics.min = static_cast<double>(*start)/runsPerIteration;
1359 //calc final metrics
1360 unsigned int n = 0;
1361 double gmean = 0;
1362 double gstddev = 0;
1363 double mean = 0;
1364 double stddev = 0;
1365 unsigned int m = 0;
1366 for(; start != end; ++start)
1367 {
1368 double x = static_cast<double>(*start)/runsPerIteration;
1369 if (x > DBL_EPSILON)
1370 {
1371 double lx = log(x);
1372 ++m;
1373 double gdelta = lx - gmean;
1374 gmean += gdelta / m;
1375 gstddev += gdelta * (lx - gmean);
1376 }
1377 ++n;
1378 double delta = x - mean;
1379 mean += delta / n;
1380 stddev += delta * (x - mean);
1381 }
1382
1383 metrics.mean = mean;
1384 metrics.gmean = exp(gmean);
1385 metrics.gstddev = m > 1 ? sqrt(gstddev / (m - 1)) : 0;
1386 metrics.stddev = n > 1 ? sqrt(stddev / (n - 1)) : 0;
1387 metrics.median = (n % 2
1388 ? (double)times[offset + n / 2]
1389 : 0.5 * (times[offset + n / 2] + times[offset + n / 2 - 1])
1390 ) / runsPerIteration;
1391
1392 return metrics;
1393 }
1394
validateMetrics()1395 void TestBase::validateMetrics()
1396 {
1397 performance_metrics& m = calcMetrics();
1398
1399 if (HasFailure()) return;
1400
1401 ASSERT_GE(m.samples, 1u)
1402 << " No time measurements was performed.\nstartTimer() and stopTimer() commands are required for performance tests.";
1403
1404 if (getCurrentPerformanceStrategy() == PERF_STRATEGY_BASE)
1405 {
1406 EXPECT_GE(m.samples, param_min_samples)
1407 << " Only a few samples are collected.\nPlease increase number of iterations or/and time limit to get reliable performance measurements.";
1408
1409 if (m.gstddev > DBL_EPSILON)
1410 {
1411 EXPECT_GT(/*m.gmean * */1., /*m.gmean * */ 2 * sinh(m.gstddev * param_max_deviation))
1412 << " Test results are not reliable ((mean-sigma,mean+sigma) deviation interval is greater than measured time interval).";
1413 }
1414
1415 EXPECT_LE(m.outliers, std::max((unsigned int)cvCeil(m.samples * param_max_outliers / 100.), 1u))
1416 << " Test results are not reliable (too many outliers).";
1417 }
1418 else if (getCurrentPerformanceStrategy() == PERF_STRATEGY_SIMPLE)
1419 {
1420 double mean = metrics.mean * 1000.0f / metrics.frequency;
1421 double median = metrics.median * 1000.0f / metrics.frequency;
1422 double stddev = metrics.stddev * 1000.0f / metrics.frequency;
1423 double percents = stddev / mean * 100.f;
1424 printf("[ PERFSTAT ] (samples = %d, mean = %.2f, median = %.2f, stddev = %.2f (%.1f%%))\n", (int)metrics.samples, mean, median, stddev, percents);
1425 }
1426 else
1427 {
1428 assert(false);
1429 }
1430 }
1431
reportMetrics(bool toJUnitXML)1432 void TestBase::reportMetrics(bool toJUnitXML)
1433 {
1434 performance_metrics& m = calcMetrics();
1435
1436 if (m.terminationReason == performance_metrics::TERM_SKIP_TEST)
1437 {
1438 if (toJUnitXML)
1439 {
1440 RecordProperty("custom_status", "skipped");
1441 }
1442 }
1443 else if (toJUnitXML)
1444 {
1445 RecordProperty("bytesIn", (int)m.bytesIn);
1446 RecordProperty("bytesOut", (int)m.bytesOut);
1447 RecordProperty("term", m.terminationReason);
1448 RecordProperty("samples", (int)m.samples);
1449 RecordProperty("outliers", (int)m.outliers);
1450 RecordProperty("frequency", cv::format("%.0f", m.frequency).c_str());
1451 RecordProperty("min", cv::format("%.0f", m.min).c_str());
1452 RecordProperty("median", cv::format("%.0f", m.median).c_str());
1453 RecordProperty("gmean", cv::format("%.0f", m.gmean).c_str());
1454 RecordProperty("gstddev", cv::format("%.6f", m.gstddev).c_str());
1455 RecordProperty("mean", cv::format("%.0f", m.mean).c_str());
1456 RecordProperty("stddev", cv::format("%.0f", m.stddev).c_str());
1457 #ifdef CV_COLLECT_IMPL_DATA
1458 if(param_collect_impl)
1459 {
1460 RecordProperty("impl_ipp", (int)(implConf.ipp || implConf.icv));
1461 RecordProperty("impl_ocl", (int)implConf.ocl);
1462 RecordProperty("impl_plain", (int)implConf.plain);
1463
1464 std::string rec_line;
1465 std::vector<cv::String> rec;
1466 rec_line.clear();
1467 rec = implConf.GetCallsForImpl(CV_IMPL_IPP|CV_IMPL_MT);
1468 for(int i=0; i<rec.size();i++ ){rec_line += rec[i].c_str(); rec_line += " ";}
1469 rec = implConf.GetCallsForImpl(CV_IMPL_IPP);
1470 for(int i=0; i<rec.size();i++ ){rec_line += rec[i].c_str(); rec_line += " ";}
1471 RecordProperty("impl_rec_ipp", rec_line.c_str());
1472
1473 rec_line.clear();
1474 rec = implConf.GetCallsForImpl(CV_IMPL_OCL);
1475 for(int i=0; i<rec.size();i++ ){rec_line += rec[i].c_str(); rec_line += " ";}
1476 RecordProperty("impl_rec_ocl", rec_line.c_str());
1477 }
1478 #endif
1479 }
1480 else
1481 {
1482 const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
1483 const char* type_param = test_info->type_param();
1484 const char* value_param = test_info->value_param();
1485
1486 #if defined(ANDROID) && defined(USE_ANDROID_LOGGING)
1487 LOGD("[ FAILED ] %s.%s", test_info->test_case_name(), test_info->name());
1488 #endif
1489
1490 if (type_param) LOGD("type = %11s", type_param);
1491 if (value_param) LOGD("params = %11s", value_param);
1492
1493 switch (m.terminationReason)
1494 {
1495 case performance_metrics::TERM_ITERATIONS:
1496 LOGD("termination reason: reached maximum number of iterations");
1497 break;
1498 case performance_metrics::TERM_TIME:
1499 LOGD("termination reason: reached time limit");
1500 break;
1501 case performance_metrics::TERM_INTERRUPT:
1502 LOGD("termination reason: aborted by the performance testing framework");
1503 break;
1504 case performance_metrics::TERM_EXCEPTION:
1505 LOGD("termination reason: unhandled exception");
1506 break;
1507 case performance_metrics::TERM_UNKNOWN:
1508 default:
1509 LOGD("termination reason: unknown");
1510 break;
1511 };
1512
1513 #ifdef CV_COLLECT_IMPL_DATA
1514 if(param_collect_impl)
1515 {
1516 LOGD("impl_ipp =%11d", (int)(implConf.ipp || implConf.icv));
1517 LOGD("impl_ocl =%11d", (int)implConf.ocl);
1518 LOGD("impl_plain =%11d", (int)implConf.plain);
1519
1520 std::string rec_line;
1521 std::vector<cv::String> rec;
1522 rec_line.clear();
1523 rec = implConf.GetCallsForImpl(CV_IMPL_IPP|CV_IMPL_MT);
1524 for(int i=0; i<rec.size();i++ ){rec_line += rec[i].c_str(); rec_line += " ";}
1525 rec = implConf.GetCallsForImpl(CV_IMPL_IPP);
1526 for(int i=0; i<rec.size();i++ ){rec_line += rec[i].c_str(); rec_line += " ";}
1527 LOGD("impl_rec_ipp =%s", rec_line.c_str());
1528
1529 rec_line.clear();
1530 rec = implConf.GetCallsForImpl(CV_IMPL_OCL);
1531 for(int i=0; i<rec.size();i++ ){rec_line += rec[i].c_str(); rec_line += " ";}
1532 LOGD("impl_rec_ocl =%s", rec_line.c_str());
1533 }
1534 #endif
1535
1536 LOGD("bytesIn =%11lu", (unsigned long)m.bytesIn);
1537 LOGD("bytesOut =%11lu", (unsigned long)m.bytesOut);
1538 if (nIters == (unsigned int)-1 || m.terminationReason == performance_metrics::TERM_ITERATIONS)
1539 LOGD("samples =%11u", m.samples);
1540 else
1541 LOGD("samples =%11u of %u", m.samples, nIters);
1542 LOGD("outliers =%11u", m.outliers);
1543 LOGD("frequency =%11.0f", m.frequency);
1544 if (m.samples > 0)
1545 {
1546 LOGD("min =%11.0f = %.2fms", m.min, m.min * 1e3 / m.frequency);
1547 LOGD("median =%11.0f = %.2fms", m.median, m.median * 1e3 / m.frequency);
1548 LOGD("gmean =%11.0f = %.2fms", m.gmean, m.gmean * 1e3 / m.frequency);
1549 LOGD("gstddev =%11.8f = %.2fms for 97%% dispersion interval", m.gstddev, m.gmean * 2 * sinh(m.gstddev * 3) * 1e3 / m.frequency);
1550 LOGD("mean =%11.0f = %.2fms", m.mean, m.mean * 1e3 / m.frequency);
1551 LOGD("stddev =%11.0f = %.2fms", m.stddev, m.stddev * 1e3 / m.frequency);
1552 }
1553 }
1554 }
1555
SetUp()1556 void TestBase::SetUp()
1557 {
1558 cv::theRNG().state = param_seed; // this rng should generate same numbers for each run
1559
1560 if (param_threads >= 0)
1561 cv::setNumThreads(param_threads);
1562
1563 #ifdef ANDROID
1564 if (param_affinity_mask)
1565 setCurrentThreadAffinityMask(param_affinity_mask);
1566 #endif
1567
1568 verified = false;
1569 lastTime = 0;
1570 totalTime = 0;
1571 runsPerIteration = 1;
1572 nIters = iterationsLimitDefault;
1573 currentIter = (unsigned int)-1;
1574 timeLimit = timeLimitDefault;
1575 times.clear();
1576 }
1577
TearDown()1578 void TestBase::TearDown()
1579 {
1580 if (metrics.terminationReason == performance_metrics::TERM_SKIP_TEST)
1581 {
1582 LOGI("\tTest was skipped");
1583 GTEST_SUCCEED() << "Test was skipped";
1584 }
1585 else
1586 {
1587 if (!HasFailure() && !verified)
1588 ADD_FAILURE() << "The test has no sanity checks. There should be at least one check at the end of performance test.";
1589
1590 validateMetrics();
1591 if (HasFailure())
1592 {
1593 reportMetrics(false);
1594 return;
1595 }
1596 }
1597
1598 const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
1599 const char* type_param = test_info->type_param();
1600 const char* value_param = test_info->value_param();
1601 if (value_param) printf("[ VALUE ] \t%s\n", value_param), fflush(stdout);
1602 if (type_param) printf("[ TYPE ] \t%s\n", type_param), fflush(stdout);
1603
1604 #ifdef CV_COLLECT_IMPL_DATA
1605 if(param_collect_impl)
1606 {
1607 implConf.ShapeUp();
1608 printf("[ I. FLAGS ] \t");
1609 if(implConf.ipp_mt)
1610 {
1611 if(implConf.icv) {printf("ICV_MT "); std::vector<cv::String> fun = implConf.GetCallsForImpl(CV_IMPL_IPP|CV_IMPL_MT); printf("("); for(int i=0; i<fun.size();i++ ){printf("%s ", fun[i].c_str());} printf(") "); }
1612 if(implConf.ipp) {printf("IPP_MT "); std::vector<cv::String> fun = implConf.GetCallsForImpl(CV_IMPL_IPP|CV_IMPL_MT); printf("("); for(int i=0; i<fun.size();i++ ){printf("%s ", fun[i].c_str());} printf(") "); }
1613 }
1614 else
1615 {
1616 if(implConf.icv) {printf("ICV "); std::vector<cv::String> fun = implConf.GetCallsForImpl(CV_IMPL_IPP); printf("("); for(int i=0; i<fun.size();i++ ){printf("%s ", fun[i].c_str());} printf(") "); }
1617 if(implConf.ipp) {printf("IPP "); std::vector<cv::String> fun = implConf.GetCallsForImpl(CV_IMPL_IPP); printf("("); for(int i=0; i<fun.size();i++ ){printf("%s ", fun[i].c_str());} printf(") "); }
1618 }
1619 if(implConf.ocl) {printf("OCL "); std::vector<cv::String> fun = implConf.GetCallsForImpl(CV_IMPL_OCL); printf("("); for(int i=0; i<fun.size();i++ ){printf("%s ", fun[i].c_str());} printf(") "); }
1620 if(implConf.plain) printf("PLAIN ");
1621 if(!(implConf.ipp_mt || implConf.icv || implConf.ipp || implConf.ocl || implConf.plain))
1622 printf("ERROR ");
1623 printf("\n");
1624 fflush(stdout);
1625 }
1626 #endif
1627 reportMetrics(true);
1628 }
1629
getDataPath(const std::string & relativePath)1630 std::string TestBase::getDataPath(const std::string& relativePath)
1631 {
1632 if (relativePath.empty())
1633 {
1634 ADD_FAILURE() << " Bad path to test resource";
1635 throw PerfEarlyExitException();
1636 }
1637
1638 const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");
1639 const char *path_separator = "/";
1640
1641 std::string path;
1642 if (data_path_dir)
1643 {
1644 int len = (int)strlen(data_path_dir) - 1;
1645 if (len < 0) len = 0;
1646 path = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))
1647 + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator);
1648 }
1649 else
1650 {
1651 path = ".";
1652 path += path_separator;
1653 }
1654
1655 if (relativePath[0] == '/' || relativePath[0] == '\\')
1656 path += relativePath.substr(1);
1657 else
1658 path += relativePath;
1659
1660 FILE* fp = fopen(path.c_str(), "r");
1661 if (fp)
1662 fclose(fp);
1663 else
1664 {
1665 ADD_FAILURE() << " Requested file \"" << path << "\" does not exist.";
1666 throw PerfEarlyExitException();
1667 }
1668 return path;
1669 }
1670
RunPerfTestBody()1671 void TestBase::RunPerfTestBody()
1672 {
1673 try
1674 {
1675 #ifdef CV_COLLECT_IMPL_DATA
1676 if(param_collect_impl)
1677 implConf.Reset();
1678 #endif
1679 this->PerfTestBody();
1680 #ifdef CV_COLLECT_IMPL_DATA
1681 if(param_collect_impl)
1682 implConf.GetImpl();
1683 #endif
1684 }
1685 catch(PerfSkipTestException&)
1686 {
1687 metrics.terminationReason = performance_metrics::TERM_SKIP_TEST;
1688 return;
1689 }
1690 catch(PerfEarlyExitException&)
1691 {
1692 metrics.terminationReason = performance_metrics::TERM_INTERRUPT;
1693 return;//no additional failure logging
1694 }
1695 catch(cv::Exception& e)
1696 {
1697 metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
1698 #ifdef HAVE_CUDA
1699 if (e.code == cv::Error::GpuApiCallError)
1700 cv::cuda::resetDevice();
1701 #endif
1702 FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws cv::Exception:\n " << e.what();
1703 }
1704 catch(std::exception& e)
1705 {
1706 metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
1707 FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws std::exception:\n " << e.what();
1708 }
1709 catch(...)
1710 {
1711 metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
1712 FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws...";
1713 }
1714 }
1715
1716 /*****************************************************************************************\
1717 * ::perf::TestBase::_declareHelper
1718 \*****************************************************************************************/
iterations(unsigned int n)1719 TestBase::_declareHelper& TestBase::_declareHelper::iterations(unsigned int n)
1720 {
1721 test->times.clear();
1722 test->times.reserve(n);
1723 test->nIters = std::min(n, TestBase::iterationsLimitDefault);
1724 test->currentIter = (unsigned int)-1;
1725 test->metrics.clear();
1726 return *this;
1727 }
1728
time(double timeLimitSecs)1729 TestBase::_declareHelper& TestBase::_declareHelper::time(double timeLimitSecs)
1730 {
1731 test->times.clear();
1732 test->currentIter = (unsigned int)-1;
1733 test->timeLimit = (int64)(timeLimitSecs * cv::getTickFrequency());
1734 test->metrics.clear();
1735 return *this;
1736 }
1737
tbb_threads(int n)1738 TestBase::_declareHelper& TestBase::_declareHelper::tbb_threads(int n)
1739 {
1740 cv::setNumThreads(n);
1741 return *this;
1742 }
1743
runs(unsigned int runsNumber)1744 TestBase::_declareHelper& TestBase::_declareHelper::runs(unsigned int runsNumber)
1745 {
1746 test->runsPerIteration = runsNumber;
1747 return *this;
1748 }
1749
in(cv::InputOutputArray a1,WarmUpType wtype)1750 TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, WarmUpType wtype)
1751 {
1752 if (!test->times.empty()) return *this;
1753 TestBase::declareArray(test->inputData, a1, wtype);
1754 return *this;
1755 }
1756
in(cv::InputOutputArray a1,cv::InputOutputArray a2,WarmUpType wtype)1757 TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, WarmUpType wtype)
1758 {
1759 if (!test->times.empty()) return *this;
1760 TestBase::declareArray(test->inputData, a1, wtype);
1761 TestBase::declareArray(test->inputData, a2, wtype);
1762 return *this;
1763 }
1764
in(cv::InputOutputArray a1,cv::InputOutputArray a2,cv::InputOutputArray a3,WarmUpType wtype)1765 TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, WarmUpType wtype)
1766 {
1767 if (!test->times.empty()) return *this;
1768 TestBase::declareArray(test->inputData, a1, wtype);
1769 TestBase::declareArray(test->inputData, a2, wtype);
1770 TestBase::declareArray(test->inputData, a3, wtype);
1771 return *this;
1772 }
1773
in(cv::InputOutputArray a1,cv::InputOutputArray a2,cv::InputOutputArray a3,cv::InputOutputArray a4,WarmUpType wtype)1774 TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, WarmUpType wtype)
1775 {
1776 if (!test->times.empty()) return *this;
1777 TestBase::declareArray(test->inputData, a1, wtype);
1778 TestBase::declareArray(test->inputData, a2, wtype);
1779 TestBase::declareArray(test->inputData, a3, wtype);
1780 TestBase::declareArray(test->inputData, a4, wtype);
1781 return *this;
1782 }
1783
out(cv::InputOutputArray a1,WarmUpType wtype)1784 TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, WarmUpType wtype)
1785 {
1786 if (!test->times.empty()) return *this;
1787 TestBase::declareArray(test->outputData, a1, wtype);
1788 return *this;
1789 }
1790
out(cv::InputOutputArray a1,cv::InputOutputArray a2,WarmUpType wtype)1791 TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, WarmUpType wtype)
1792 {
1793 if (!test->times.empty()) return *this;
1794 TestBase::declareArray(test->outputData, a1, wtype);
1795 TestBase::declareArray(test->outputData, a2, wtype);
1796 return *this;
1797 }
1798
out(cv::InputOutputArray a1,cv::InputOutputArray a2,cv::InputOutputArray a3,WarmUpType wtype)1799 TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, WarmUpType wtype)
1800 {
1801 if (!test->times.empty()) return *this;
1802 TestBase::declareArray(test->outputData, a1, wtype);
1803 TestBase::declareArray(test->outputData, a2, wtype);
1804 TestBase::declareArray(test->outputData, a3, wtype);
1805 return *this;
1806 }
1807
out(cv::InputOutputArray a1,cv::InputOutputArray a2,cv::InputOutputArray a3,cv::InputOutputArray a4,WarmUpType wtype)1808 TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, WarmUpType wtype)
1809 {
1810 if (!test->times.empty()) return *this;
1811 TestBase::declareArray(test->outputData, a1, wtype);
1812 TestBase::declareArray(test->outputData, a2, wtype);
1813 TestBase::declareArray(test->outputData, a3, wtype);
1814 TestBase::declareArray(test->outputData, a4, wtype);
1815 return *this;
1816 }
1817
strategy(enum PERF_STRATEGY s)1818 TestBase::_declareHelper& TestBase::_declareHelper::strategy(enum PERF_STRATEGY s)
1819 {
1820 test->testStrategy = s;
1821 return *this;
1822 }
1823
_declareHelper(TestBase * t)1824 TestBase::_declareHelper::_declareHelper(TestBase* t) : test(t)
1825 {
1826 }
1827
1828 /*****************************************************************************************\
1829 * miscellaneous
1830 \*****************************************************************************************/
1831
1832 namespace {
1833 struct KeypointComparator
1834 {
1835 std::vector<cv::KeyPoint>& pts_;
1836 comparators::KeypointGreater cmp;
1837
KeypointComparator__anon3be5dff70211::KeypointComparator1838 KeypointComparator(std::vector<cv::KeyPoint>& pts) : pts_(pts), cmp() {}
1839
operator ()__anon3be5dff70211::KeypointComparator1840 bool operator()(int idx1, int idx2) const
1841 {
1842 return cmp(pts_[idx1], pts_[idx2]);
1843 }
1844 private:
1845 const KeypointComparator& operator=(const KeypointComparator&); // quiet MSVC
1846 };
1847 }//namespace
1848
sort(std::vector<cv::KeyPoint> & pts,cv::InputOutputArray descriptors)1849 void perf::sort(std::vector<cv::KeyPoint>& pts, cv::InputOutputArray descriptors)
1850 {
1851 cv::Mat desc = descriptors.getMat();
1852
1853 CV_Assert(pts.size() == (size_t)desc.rows);
1854 cv::AutoBuffer<int> idxs(desc.rows);
1855
1856 for (int i = 0; i < desc.rows; ++i)
1857 idxs[i] = i;
1858
1859 std::sort((int*)idxs, (int*)idxs + desc.rows, KeypointComparator(pts));
1860
1861 std::vector<cv::KeyPoint> spts(pts.size());
1862 cv::Mat sdesc(desc.size(), desc.type());
1863
1864 for(int j = 0; j < desc.rows; ++j)
1865 {
1866 spts[j] = pts[idxs[j]];
1867 cv::Mat row = sdesc.row(j);
1868 desc.row(idxs[j]).copyTo(row);
1869 }
1870
1871 spts.swap(pts);
1872 sdesc.copyTo(desc);
1873 }
1874
1875 /*****************************************************************************************\
1876 * ::perf::GpuPerf
1877 \*****************************************************************************************/
targetDevice()1878 bool perf::GpuPerf::targetDevice()
1879 {
1880 return param_impl == "cuda";
1881 }
1882
1883 /*****************************************************************************************\
1884 * ::perf::PrintTo
1885 \*****************************************************************************************/
1886 namespace perf
1887 {
1888
PrintTo(const MatType & t,::std::ostream * os)1889 void PrintTo(const MatType& t, ::std::ostream* os)
1890 {
1891 switch( CV_MAT_DEPTH((int)t) )
1892 {
1893 case CV_8U: *os << "8U"; break;
1894 case CV_8S: *os << "8S"; break;
1895 case CV_16U: *os << "16U"; break;
1896 case CV_16S: *os << "16S"; break;
1897 case CV_32S: *os << "32S"; break;
1898 case CV_32F: *os << "32F"; break;
1899 case CV_64F: *os << "64F"; break;
1900 case CV_USRTYPE1: *os << "USRTYPE1"; break;
1901 default: *os << "INVALID_TYPE"; break;
1902 }
1903 *os << 'C' << CV_MAT_CN((int)t);
1904 }
1905
1906 } //namespace perf
1907
1908 /*****************************************************************************************\
1909 * ::cv::PrintTo
1910 \*****************************************************************************************/
1911 namespace cv {
1912
PrintTo(const String & str,::std::ostream * os)1913 void PrintTo(const String& str, ::std::ostream* os)
1914 {
1915 *os << "\"" << str << "\"";
1916 }
1917
PrintTo(const Size & sz,::std::ostream * os)1918 void PrintTo(const Size& sz, ::std::ostream* os)
1919 {
1920 *os << /*"Size:" << */sz.width << "x" << sz.height;
1921 }
1922
1923 } // namespace cv
1924