From 3723e136b1f973f0a731c0a58d61b732b82eb81a Mon Sep 17 00:00:00 2001 From: Mike Dussault Date: Mon, 11 Oct 2021 19:44:58 +0000 Subject: [PATCH 01/10] Initial benchmark exe --- src/CMakeLists.txt | 8 +++++ src/zmbenchmark.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 src/zmbenchmark.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5f37ff0ab..80e09e9c3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -107,6 +107,7 @@ endif() add_executable(zmc zmc.cpp) add_executable(zms zms.cpp) add_executable(zmu zmu.cpp) +add_executable(zmbenchmark zmbenchmark.cpp) target_link_libraries(zmc PRIVATE @@ -129,6 +130,13 @@ target_link_libraries(zmu ${ZM_EXTRA_LIBS} ${CMAKE_DL_LIBS}) +target_link_libraries(zmbenchmark + PRIVATE + zm-core-interface + zm + ${ZM_EXTRA_LIBS} + ${CMAKE_DL_LIBS}) + # Generate man files for the binaries destined for the bin folder if(BUILD_MAN) foreach(CBINARY zmc zmu) diff --git a/src/zmbenchmark.cpp b/src/zmbenchmark.cpp new file mode 100644 index 000000000..a12462a50 --- /dev/null +++ b/src/zmbenchmark.cpp @@ -0,0 +1,76 @@ +// +// ZoneMinder Benchmark, $Date$, $Revision$ +// Copyright (C) 2001-2008 Philip Coombes +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// + +#include +#include + +#include "zm_image.h" +#include "zm_zone.h" + + + +// +// Generate a greyscale image that simulates a delta that can be fed to +// Zone::CheckAlarms. +// +// Args: +// minVal: 0-255 value telling the minimum (random) value to initialize +// all the pixels to. +// maxVal: 0-255 value telling the maximum (random) value to initialize +// all the pixels to. +// width: The width of the new image. +// height: The height of the new image. +// +// Return: +// An image with all pixels initialized to values in the [minVal,maxVal] range. +// +std::shared_ptr GenerateDeltaImage( + int minVal, + int maxVal, + int width = 3840, + int height = 2160) { + // Create the image. + Image *image = new Image( + width, + height, + ZM_COLOUR_GRAY8, + ZM_SUBPIX_ORDER_NONE); + + const int range = maxVal - minVal + 1; + for (int y=0; y < height; y++) + { + uint8_t *row = (uint8_t*)image->Buffer(0, y); + for (int x=0; x < width; x++) + row[x] = (rand() * range) / RAND_MAX + minVal; + } + + return image; +} + + +int main(int argc, char *argv[]) { + srand(111); + + RunZoneBenchmark("0%% delta", GenerateDeltaImage(0, 0)); + RunZoneBenchmark("50%% delta", GenerateDeltaImage(0, 255)); + RunZoneBenchmark("100%% delta", GenerateDeltaImage(255, 255)); + + return 0; +} + From 305af08112fec3e0cdb047c6912cf01ee86a5305 Mon Sep 17 00:00:00 2001 From: Mike Dussault Date: Mon, 11 Oct 2021 20:22:10 +0000 Subject: [PATCH 02/10] Added Monitor scaffolding and a skeleton benchmark for DetectMotion. --- src/zmbenchmark.cpp | 157 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 151 insertions(+), 6 deletions(-) diff --git a/src/zmbenchmark.cpp b/src/zmbenchmark.cpp index a12462a50..dde4fa69a 100644 --- a/src/zmbenchmark.cpp +++ b/src/zmbenchmark.cpp @@ -20,11 +20,13 @@ #include #include +#include "zm_config.h" #include "zm_image.h" +#include "zm_monitor.h" +#include "zm_utils.h" #include "zm_zone.h" - // // Generate a greyscale image that simulates a delta that can be fed to // Zone::CheckAlarms. @@ -40,7 +42,7 @@ // Return: // An image with all pixels initialized to values in the [minVal,maxVal] range. // -std::shared_ptr GenerateDeltaImage( +std::shared_ptr GenerateRandomImage( int minVal, int maxVal, int width = 3840, @@ -60,16 +62,159 @@ std::shared_ptr GenerateDeltaImage( row[x] = (rand() * range) / RAND_MAX + minVal; } - return image; + return std::shared_ptr(image); +} + + +// +// This is used to help rig up tests of Monitor. +// +class TestMonitor : public Monitor +{ +public: + TestMonitor(int width, int height) { + cur_zone_id = 111; + + this->width = width; + this->height = height; + + // Create a dummy ref_image. + std::shared_ptr tempImage = GenerateRandomImage(0, 0, width, height); + ref_image = *tempImage.get(); + + shared_data = &temp_shared_data; + } + + // + // Add a new zone to this monitor. + // + // Args: + // checkMethod: This controls how this zone will actually do motion detection. + // + // p_filter_box: The size of the filter to use. + // + void AddZone(Zone::CheckMethod checkMethod, const Vector2 &p_filter_box=Vector2(5,5)) { + const int p_min_pixel_threshold = 50; + const int p_max_pixel_threshold = 255; + const int p_min_alarm_pixels = 1000; + const int p_max_alarm_pixels = 10000000; + + const int zone_id = cur_zone_id++; + const std::string zone_label = std::string("zone_") + std::to_string(zone_id); + const Zone::ZoneType zoneType = Zone::ZoneType::ACTIVE; + const Polygon poly({ + Vector2(0, 0), + Vector2(width-1, 0), + Vector2(width-1, height-1), + Vector2(0, height-1)}); + + Zone zone( + this, + zone_id, + zone_label.c_str(), + zoneType, + poly, + kRGBGreen, + Zone::CheckMethod::FILTERED_PIXELS, + p_min_pixel_threshold, + p_max_pixel_threshold, + p_min_alarm_pixels, + p_max_alarm_pixels, + p_filter_box + ); + zones.push_back(zone); + } + + void SetRefImage(const Image *image) { + ref_image = *image; + } + +private: + SharedData temp_shared_data; + int cur_zone_id; +}; + + + +class CounterInfo { +public: + CounterInfo( + const std::chrono::microseconds &in_timer, + const std::string &in_label) : + timer(in_timer), + label(in_label) + { + } + + const std::chrono::microseconds timer; + const std::string label; +}; + +// +// Print out a table of timing results. +// +// Args: +// counters: The list of counters to print out, and info about how to format it. +// +void PrintCounters(std::vector counters) { + for (const auto counter : counters) { + printf("%s: %lims\n", counter.label.c_str(), counter.timer.count()); + } +} + +// +// Run zone benchmarks on the given image. +// +// Args: +// label: A label to be printed before the output. +// +// image: The image to run the tests on. +// +void RunZoneBenchmark(const char *label, std::shared_ptr image) { + // Create a monitor to use for the benchmark. Give it 1 zone that uses + // a 5x5 filter. + TestMonitor testMonitor(image->Width(), image->Height()); + testMonitor.AddZone(Zone::CheckMethod::FILTERED_PIXELS, Vector2(5,5)); + + // Generate a black image to use as the reference image. + std::shared_ptr blackImage = GenerateRandomImage( + 0, 0, image->Width(), image->Height()); + testMonitor.SetRefImage(blackImage.get()); + + std::chrono::microseconds totalTimeTaken(0); + + // Run a series of passes over DetectMotion. + const int numPasses = 10; + for (int i=0; i < numPasses; i++) + { + printf("\r(%d / %d) ", i+1, numPasses); + fflush(stdout); + + Event::StringSet zoneSet; + testMonitor.DetectMotion(*image.get(), zoneSet); + } + + printf("\n"); + printf("------- %s -------\n", label); + PrintCounters({ + CounterInfo(totalTimeTaken, "Total zone benchmark time")}); } int main(int argc, char *argv[]) { srand(111); - RunZoneBenchmark("0%% delta", GenerateDeltaImage(0, 0)); - RunZoneBenchmark("50%% delta", GenerateDeltaImage(0, 255)); - RunZoneBenchmark("100%% delta", GenerateDeltaImage(255, 255)); + // Init global stuff that we need. + config.font_file_location = "../fonts/default.zmfnt"; + config.event_close_mode = "time"; + config.cpu_extensions = 1; + + // Detect SSE version. + HwCapsDetect(); + + RunZoneBenchmark("0%% delta", GenerateRandomImage(0, 0)); + RunZoneBenchmark("50%% delta", GenerateRandomImage(0, 255)); + RunZoneBenchmark("100%% delta", GenerateRandomImage(255, 255)); return 0; } From 9370cfe25c6be22e7505a6f38deb4559416f091c Mon Sep 17 00:00:00 2001 From: Mike Dussault Date: Mon, 11 Oct 2021 21:12:46 +0000 Subject: [PATCH 03/10] Added TimeSegmentAdder class and got benchmark output looking ok. --- src/zm_time.h | 38 ++++++++++++++++++++++++++++++++++++++ src/zmbenchmark.cpp | 29 +++++++++++++++++------------ 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/zm_time.h b/src/zm_time.h index f7574bbf7..ab2a8d000 100644 --- a/src/zm_time.h +++ b/src/zm_time.h @@ -83,4 +83,42 @@ Duration duration_cast(timeval const &tv) { } } +// +// This can be used for benchmarking. It will measure the time in between +// its constructor and destructor (or when you call Finish()) and add that +// duration to a microseconds clock. +// +class TimeSegmentAdder { +public: + TimeSegmentAdder(Microseconds &inTarget) : + target(inTarget), + startTime(std::chrono::steady_clock::now()), + finished(false) { + } + + ~TimeSegmentAdder() { + Finish(); + } + + // Call this to stop the timer and add the timed duration to `target`. + void Finish() { + if (!finished) { + const TimePoint endTime = std::chrono::steady_clock::now(); + target += (std::chrono::duration_cast(endTime - startTime)); + } + finished = true; + } + +private: + // This is where we will add our duration to. + Microseconds ⌖ + + // The time we started. + const TimePoint startTime; + + // True when it has finished timing. + bool finished; +}; + + #endif // ZM_TIME_H diff --git a/src/zmbenchmark.cpp b/src/zmbenchmark.cpp index dde4fa69a..90a2316b3 100644 --- a/src/zmbenchmark.cpp +++ b/src/zmbenchmark.cpp @@ -23,6 +23,7 @@ #include "zm_config.h" #include "zm_image.h" #include "zm_monitor.h" +#include "zm_time.h" #include "zm_utils.h" #include "zm_zone.h" @@ -67,7 +68,7 @@ std::shared_ptr GenerateRandomImage( // -// This is used to help rig up tests of Monitor. +// This is used to help rig up Monitor benchmarks. // class TestMonitor : public Monitor { @@ -139,14 +140,14 @@ private: class CounterInfo { public: CounterInfo( - const std::chrono::microseconds &in_timer, + const Microseconds in_timer, const std::string &in_label) : timer(in_timer), label(in_label) { } - const std::chrono::microseconds timer; + const Microseconds timer; const std::string label; }; @@ -158,7 +159,7 @@ public: // void PrintCounters(std::vector counters) { for (const auto counter : counters) { - printf("%s: %lims\n", counter.label.c_str(), counter.timer.count()); + printf("%s: %liµs\n", counter.label.c_str(), counter.timer.count()); } } @@ -181,23 +182,27 @@ void RunZoneBenchmark(const char *label, std::shared_ptr image) { 0, 0, image->Width(), image->Height()); testMonitor.SetRefImage(blackImage.get()); - std::chrono::microseconds totalTimeTaken(0); + Microseconds totalTimeTaken(0); + + printf("\n"); + printf("------- %s -------\n", label); // Run a series of passes over DetectMotion. const int numPasses = 10; for (int i=0; i < numPasses; i++) { - printf("\r(%d / %d) ", i+1, numPasses); + printf("\rPass %2d / %2d ", i+1, numPasses); fflush(stdout); + TimeSegmentAdder adder(totalTimeTaken); + Event::StringSet zoneSet; testMonitor.DetectMotion(*image.get(), zoneSet); } - printf("\n"); - printf("------- %s -------\n", label); + PrintCounters({ - CounterInfo(totalTimeTaken, "Total zone benchmark time")}); + CounterInfo(totalTimeTaken / numPasses, "Time per pass")}); } @@ -212,9 +217,9 @@ int main(int argc, char *argv[]) { // Detect SSE version. HwCapsDetect(); - RunZoneBenchmark("0%% delta", GenerateRandomImage(0, 0)); - RunZoneBenchmark("50%% delta", GenerateRandomImage(0, 255)); - RunZoneBenchmark("100%% delta", GenerateRandomImage(255, 255)); + RunZoneBenchmark("0% delta", GenerateRandomImage(0, 0)); + RunZoneBenchmark("50% delta", GenerateRandomImage(0, 255)); + RunZoneBenchmark("100% delta", GenerateRandomImage(255, 255)); return 0; } From e4542de6f2affd878060267057eee26a663f7404 Mon Sep 17 00:00:00 2001 From: Mike Dussault Date: Mon, 11 Oct 2021 14:21:08 -0700 Subject: [PATCH 04/10] Fixed random image generation, and now it shows a proper (giant) perf difference between images that take longer for Zone's FILTERED_PIXELS detection to deal with. --- src/zmbenchmark.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/zmbenchmark.cpp b/src/zmbenchmark.cpp index 90a2316b3..d194fc0a8 100644 --- a/src/zmbenchmark.cpp +++ b/src/zmbenchmark.cpp @@ -55,12 +55,16 @@ std::shared_ptr GenerateRandomImage( ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE); - const int range = maxVal - minVal + 1; + const int randMax = RAND_MAX; + const int range = maxVal - minVal; + for (int y=0; y < height; y++) { uint8_t *row = (uint8_t*)image->Buffer(0, y); - for (int x=0; x < width; x++) - row[x] = (rand() * range) / RAND_MAX + minVal; + for (int x=0; x < width; x++) { + uint64_t randVal = rand(); + row[x] = (uint8_t)((randVal * range) / randMax + minVal); + } } return std::shared_ptr(image); From 146ff1ac7aad48a2cedff972795dd1b5c2e31c3d Mon Sep 17 00:00:00 2001 From: Mike Dussault Date: Mon, 11 Oct 2021 15:53:29 -0700 Subject: [PATCH 05/10] Print the benchmark results in a nicer table --- src/zmbenchmark.cpp | 171 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 142 insertions(+), 29 deletions(-) diff --git a/src/zmbenchmark.cpp b/src/zmbenchmark.cpp index d194fc0a8..b142879eb 100644 --- a/src/zmbenchmark.cpp +++ b/src/zmbenchmark.cpp @@ -17,6 +17,8 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // +#include +#include #include #include @@ -28,15 +30,87 @@ #include "zm_zone.h" +// +// This allows you to feed in a set of columns and timing rows, and print it +// out in a nice-looking table. +// +class TimingsTable { +public: + TimingsTable(const std::vector &inColumns) : columns(inColumns) {} + + void AddRow(const std::string &label, const std::vector &timings) { + assert(timings.size() == columns.size()); + Row row; + row.label = label; + row.timings = timings; + rows.push_back(row); + } + + void Print(const int columnPad = 5) { + // Figure out column widths. + std::vector widths(columns.size() + 1); + + // The first width is the max of the row labels. + auto result = std::max_element(rows.begin(), rows.end(), [](const Row &a, const Row &b) -> bool { return a.label.length() < b.label.length(); }); + widths[0] = result->label.length() + columnPad; + + // Calculate the rest of the column widths. + for (size_t i=0; i < columns.size(); i++) + widths[i+1] = columns[i].length() + columnPad; + + auto PrintColStr = [&](size_t icol, const std::string &str) { + printf("%s", str.c_str()); + PrintPadding(widths[icol] - str.length()); + }; + + // Print the header. + PrintColStr(0, ""); + for (size_t i=0; i < columns.size(); i++) { + PrintColStr(i+1, columns[i]); + } + printf("\n"); + + // Print the timings rows. + for (const auto &row : rows) { + PrintColStr(0, row.label); + + for (size_t i=0; i < row.timings.size(); i++) { + char num[128]; + sprintf(num, "%.2f", row.timings[i].count() / 1000.0); + PrintColStr(i+1, num); + } + + printf("\n"); + } + } + +private: + void PrintPadding(int count) { + std::string str(count, ' '); + printf("%s", str.c_str()); + } + + class Row { + public: + std::string label; + std::vector timings; + }; + + std::vector columns; + std::vector rows; +}; + + + // // Generate a greyscale image that simulates a delta that can be fed to -// Zone::CheckAlarms. +// Zone::CheckAlarms. This first creates a black image, and then it fills +// a box of a certain size inside the image with random data. This is to simulate +// a typical scene where most of the scene doesn't change except a specific region. // // Args: -// minVal: 0-255 value telling the minimum (random) value to initialize -// all the pixels to. -// maxVal: 0-255 value telling the maximum (random) value to initialize -// all the pixels to. +// changeBoxPercent: 0-100 value telling how large the box with random data should be. +// Set to 0 to leave the whole thing black. // width: The width of the new image. // height: The height of the new image. // @@ -44,10 +118,9 @@ // An image with all pixels initialized to values in the [minVal,maxVal] range. // std::shared_ptr GenerateRandomImage( - int minVal, - int maxVal, - int width = 3840, - int height = 2160) { + const int changeBoxPercent, + const int width = 3840, + const int height = 2160) { // Create the image. Image *image = new Image( width, @@ -55,15 +128,20 @@ std::shared_ptr GenerateRandomImage( ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE); - const int randMax = RAND_MAX; - const int range = maxVal - minVal; + // Set it to black initially. + memset((void*)image->Buffer(0, 0), 0, image->LineSize() * image->Height()); - for (int y=0; y < height; y++) + // Now randomize the pixels inside a box. + const int boxWidth = (width * changeBoxPercent) / 100; + const int boxHeight = (height * changeBoxPercent) / 100; + const int boxX = (int)((uint64_t)rand() * (width - boxWidth) / RAND_MAX); + const int boxY = (int)((uint64_t)rand() * (height - boxHeight) / RAND_MAX); + + for (int y=0; y < boxHeight; y++) { - uint8_t *row = (uint8_t*)image->Buffer(0, y); - for (int x=0; x < width; x++) { - uint64_t randVal = rand(); - row[x] = (uint8_t)((randVal * range) / randMax + minVal); + uint8_t *row = (uint8_t*)image->Buffer(boxX, boxY + y); + for (int x=0; x < boxWidth; x++) { + row[x] = (uint8_t)rand(); } } @@ -84,7 +162,7 @@ public: this->height = height; // Create a dummy ref_image. - std::shared_ptr tempImage = GenerateRandomImage(0, 0, width, height); + std::shared_ptr tempImage = GenerateRandomImage(0, width, height); ref_image = *tempImage.get(); shared_data = &temp_shared_data; @@ -174,28 +252,33 @@ void PrintCounters(std::vector counters) { // label: A label to be printed before the output. // // image: The image to run the tests on. +// +// p_filter_box: The size of the filter to use for alarm detection. +// +// Return: +// The average time taken for each DetectMotion call. // -void RunZoneBenchmark(const char *label, std::shared_ptr image) { +Microseconds RunDetectMotionBenchmark( + const std::string &label, + std::shared_ptr image, + const Vector2 &p_filter_box) { // Create a monitor to use for the benchmark. Give it 1 zone that uses // a 5x5 filter. TestMonitor testMonitor(image->Width(), image->Height()); - testMonitor.AddZone(Zone::CheckMethod::FILTERED_PIXELS, Vector2(5,5)); + testMonitor.AddZone(Zone::CheckMethod::FILTERED_PIXELS, p_filter_box); // Generate a black image to use as the reference image. std::shared_ptr blackImage = GenerateRandomImage( - 0, 0, image->Width(), image->Height()); + 0, image->Width(), image->Height()); testMonitor.SetRefImage(blackImage.get()); Microseconds totalTimeTaken(0); - printf("\n"); - printf("------- %s -------\n", label); - // Run a series of passes over DetectMotion. const int numPasses = 10; for (int i=0; i < numPasses; i++) { - printf("\rPass %2d / %2d ", i+1, numPasses); + printf("\r%s - pass %2d / %2d ", label.c_str(), i+1, numPasses); fflush(stdout); TimeSegmentAdder adder(totalTimeTaken); @@ -205,8 +288,27 @@ void RunZoneBenchmark(const char *label, std::shared_ptr image) { } printf("\n"); - PrintCounters({ - CounterInfo(totalTimeTaken / numPasses, "Time per pass")}); + return totalTimeTaken / numPasses; +} + + +void RunDetectMotionBenchmarks( + TimingsTable &table, + const std::vector &deltaBoxPercents, + const Vector2 &p_filter_box) { + std::vector timings; + + for (int percent : deltaBoxPercents) { + Microseconds timing = RunDetectMotionBenchmark( + std::string("DetectMotion: ") + std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + " box, " + std::to_string(percent) + "% delta", + GenerateRandomImage(percent), + p_filter_box); + timings.push_back(timing); + } + + table.AddRow( + std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + " filter", + timings); } @@ -220,11 +322,22 @@ int main(int argc, char *argv[]) { // Detect SSE version. HwCapsDetect(); + + // Setup the column titles for the TimingsTable we'll generate. + // Each column represents how large the box in the image is with delta pixels. + // Each row represents a different filter size. + const std::vector percents = {0, 10, 50, 100}; + std::vector columns(percents.size()); + std::transform(percents.begin(), percents.end(), columns.begin(), + [](const int percent) {return std::to_string(percent) + "% delta (ms)";}); + TimingsTable table(columns); - RunZoneBenchmark("0% delta", GenerateRandomImage(0, 0)); - RunZoneBenchmark("50% delta", GenerateRandomImage(0, 255)); - RunZoneBenchmark("100% delta", GenerateRandomImage(255, 255)); + std::vector filterSizes = {Vector2(3,3), Vector2(5,5), Vector2(13,13)}; + for (const auto filterSize : filterSizes) { + RunDetectMotionBenchmarks(table, percents, filterSize); + } + table.Print(); return 0; } From 2cf16eb49537b08f7e7f3f8a4b8c8cdae76ea461 Mon Sep 17 00:00:00 2001 From: Mike Dussault Date: Mon, 11 Oct 2021 15:58:30 -0700 Subject: [PATCH 06/10] Removed some unused code and added more function comments --- src/zmbenchmark.cpp | 51 ++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/zmbenchmark.cpp b/src/zmbenchmark.cpp index b142879eb..b8a02ff69 100644 --- a/src/zmbenchmark.cpp +++ b/src/zmbenchmark.cpp @@ -38,6 +38,12 @@ class TimingsTable { public: TimingsTable(const std::vector &inColumns) : columns(inColumns) {} + // + // Add a row to the end of the table. + // + // Args: + // label: The name of the row (printed in the first column). + // timings: The values for all the other columns in this row. void AddRow(const std::string &label, const std::vector &timings) { assert(timings.size() == columns.size()); Row row; @@ -46,6 +52,12 @@ public: rows.push_back(row); } + // + // Print out the table. + // + // Args: + // columnPad: # characters between table columns + // void Print(const int columnPad = 5) { // Figure out column widths. std::vector widths(columns.size() + 1); @@ -219,32 +231,6 @@ private: -class CounterInfo { -public: - CounterInfo( - const Microseconds in_timer, - const std::string &in_label) : - timer(in_timer), - label(in_label) - { - } - - const Microseconds timer; - const std::string label; -}; - -// -// Print out a table of timing results. -// -// Args: -// counters: The list of counters to print out, and info about how to format it. -// -void PrintCounters(std::vector counters) { - for (const auto counter : counters) { - printf("%s: %liµs\n", counter.label.c_str(), counter.timer.count()); - } -} - // // Run zone benchmarks on the given image. // @@ -292,6 +278,19 @@ Microseconds RunDetectMotionBenchmark( } +// +// This runs a set of Monitor::DetectMotion benchmarks, one for each of the +// "delta box percents" that are passed in. This adds one row to the +// TimingsTable specified. +// +// Args: +// table: The table to add timings into. +// +// deltaBoxPercents: Each of these defines a box size in the delta images +// passed to DetectMotion (larger boxes make it slower, sometimes significantly so). +// +// p_filter_box: Defines the filter size used in DetectMotion. +// void RunDetectMotionBenchmarks( TimingsTable &table, const std::vector &deltaBoxPercents, From 814124251e3e4710ec411ff8e306e4346124382b Mon Sep 17 00:00:00 2001 From: Mike Dussault Date: Mon, 11 Oct 2021 23:57:20 +0000 Subject: [PATCH 07/10] Fixed a CodeQL complaint --- src/zmbenchmark.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zmbenchmark.cpp b/src/zmbenchmark.cpp index b8a02ff69..2c19d0b6f 100644 --- a/src/zmbenchmark.cpp +++ b/src/zmbenchmark.cpp @@ -141,7 +141,7 @@ std::shared_ptr GenerateRandomImage( ZM_SUBPIX_ORDER_NONE); // Set it to black initially. - memset((void*)image->Buffer(0, 0), 0, image->LineSize() * image->Height()); + memset((void*)image->Buffer(0, 0), 0, (size_t)image->LineSize() * (size_t)image->Height()); // Now randomize the pixels inside a box. const int boxWidth = (width * changeBoxPercent) / 100; From 97b38bd4abe9fa7fcf69e6f68701f8a438aa6c25 Mon Sep 17 00:00:00 2001 From: Peter Keresztes Schmidt Date: Tue, 12 Oct 2021 18:53:31 +0200 Subject: [PATCH 08/10] Align changes with our codestyle See https://google.github.io/styleguide/cppguide.html for reference --- src/zm_time.h | 27 +++---- src/zmbenchmark.cpp | 190 ++++++++++++++++++++------------------------ 2 files changed, 100 insertions(+), 117 deletions(-) diff --git a/src/zm_time.h b/src/zm_time.h index ab2a8d000..d3d5b95c5 100644 --- a/src/zm_time.h +++ b/src/zm_time.h @@ -89,11 +89,11 @@ Duration duration_cast(timeval const &tv) { // duration to a microseconds clock. // class TimeSegmentAdder { -public: - TimeSegmentAdder(Microseconds &inTarget) : - target(inTarget), - startTime(std::chrono::steady_clock::now()), - finished(false) { + public: + TimeSegmentAdder(Microseconds &in_target) : + target_(in_target), + start_time_(std::chrono::steady_clock::now()), + finished_(false) { } ~TimeSegmentAdder() { @@ -102,23 +102,22 @@ public: // Call this to stop the timer and add the timed duration to `target`. void Finish() { - if (!finished) { - const TimePoint endTime = std::chrono::steady_clock::now(); - target += (std::chrono::duration_cast(endTime - startTime)); + if (!finished_) { + const TimePoint end_time = std::chrono::steady_clock::now(); + target_ += (std::chrono::duration_cast(end_time - start_time_)); } - finished = true; + finished_ = true; } -private: + private: // This is where we will add our duration to. - Microseconds ⌖ + Microseconds &target_; // The time we started. - const TimePoint startTime; + const TimePoint start_time_; // True when it has finished timing. - bool finished; + bool finished_; }; - #endif // ZM_TIME_H diff --git a/src/zmbenchmark.cpp b/src/zmbenchmark.cpp index 2c19d0b6f..2e9bd7678 100644 --- a/src/zmbenchmark.cpp +++ b/src/zmbenchmark.cpp @@ -18,9 +18,10 @@ // #include -#include +#include #include -#include +#include +#include #include "zm_config.h" #include "zm_image.h" @@ -29,14 +30,13 @@ #include "zm_utils.h" #include "zm_zone.h" - // // This allows you to feed in a set of columns and timing rows, and print it // out in a nice-looking table. // class TimingsTable { -public: - TimingsTable(const std::vector &inColumns) : columns(inColumns) {} + public: + explicit TimingsTable(std::vector in_columns) : columns_(std::move(in_columns)) {} // // Add a row to the end of the table. @@ -45,11 +45,11 @@ public: // label: The name of the row (printed in the first column). // timings: The values for all the other columns in this row. void AddRow(const std::string &label, const std::vector &timings) { - assert(timings.size() == columns.size()); + assert(timings.size() == columns_.size()); Row row; row.label = label; row.timings = timings; - rows.push_back(row); + rows_.push_back(row); } // @@ -58,17 +58,21 @@ public: // Args: // columnPad: # characters between table columns // - void Print(const int columnPad = 5) { + void Print(const int column_pad = 5) { // Figure out column widths. - std::vector widths(columns.size() + 1); + std::vector widths(columns_.size() + 1); // The first width is the max of the row labels. - auto result = std::max_element(rows.begin(), rows.end(), [](const Row &a, const Row &b) -> bool { return a.label.length() < b.label.length(); }); - widths[0] = result->label.length() + columnPad; + auto result = std::max_element(rows_.begin(), + rows_.end(), + [](const Row &a, const Row &b) -> bool { + return a.label.length() < b.label.length(); + }); + widths[0] = result->label.length() + column_pad; // Calculate the rest of the column widths. - for (size_t i=0; i < columns.size(); i++) - widths[i+1] = columns[i].length() + columnPad; + for (size_t i = 0 ; i < columns_.size() ; i++) + widths[i + 1] = columns_[i].length() + column_pad; auto PrintColStr = [&](size_t icol, const std::string &str) { printf("%s", str.c_str()); @@ -77,43 +81,40 @@ public: // Print the header. PrintColStr(0, ""); - for (size_t i=0; i < columns.size(); i++) { - PrintColStr(i+1, columns[i]); + for (size_t i = 0 ; i < columns_.size() ; i++) { + PrintColStr(i + 1, columns_[i]); } printf("\n"); // Print the timings rows. - for (const auto &row : rows) { + for (const Row &row : rows_) { PrintColStr(0, row.label); - for (size_t i=0; i < row.timings.size(); i++) { + for (size_t i = 0 ; i < row.timings.size() ; i++) { char num[128]; sprintf(num, "%.2f", row.timings[i].count() / 1000.0); - PrintColStr(i+1, num); + PrintColStr(i + 1, num); } printf("\n"); } } -private: - void PrintPadding(int count) { + private: + static void PrintPadding(int count) { std::string str(count, ' '); printf("%s", str.c_str()); } - class Row { - public: + struct Row { std::string label; std::vector timings; }; - std::vector columns; - std::vector rows; + std::vector columns_; + std::vector rows_; }; - - // // Generate a greyscale image that simulates a delta that can be fed to // Zone::CheckAlarms. This first creates a black image, and then it fills @@ -130,52 +131,43 @@ private: // An image with all pixels initialized to values in the [minVal,maxVal] range. // std::shared_ptr GenerateRandomImage( - const int changeBoxPercent, + const int change_box_percent, const int width = 3840, const int height = 2160) { // Create the image. - Image *image = new Image( - width, - height, - ZM_COLOUR_GRAY8, - ZM_SUBPIX_ORDER_NONE); + Image *image = new Image(width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE); // Set it to black initially. - memset((void*)image->Buffer(0, 0), 0, (size_t)image->LineSize() * (size_t)image->Height()); + memset((void *) image->Buffer(0, 0), 0, (size_t) image->LineSize() * (size_t) image->Height()); // Now randomize the pixels inside a box. - const int boxWidth = (width * changeBoxPercent) / 100; - const int boxHeight = (height * changeBoxPercent) / 100; - const int boxX = (int)((uint64_t)rand() * (width - boxWidth) / RAND_MAX); - const int boxY = (int)((uint64_t)rand() * (height - boxHeight) / RAND_MAX); + const int box_width = (width * change_box_percent) / 100; + const int box_height = (height * change_box_percent) / 100; + const int box_x = (int) ((uint64_t) rand() * (width - box_width) / RAND_MAX); + const int box_y = (int) ((uint64_t) rand() * (height - box_height) / RAND_MAX); - for (int y=0; y < boxHeight; y++) - { - uint8_t *row = (uint8_t*)image->Buffer(boxX, boxY + y); - for (int x=0; x < boxWidth; x++) { - row[x] = (uint8_t)rand(); + for (int y = 0 ; y < box_height ; y++) { + uint8_t *row = (uint8_t *) image->Buffer(box_x, box_y + y); + for (int x = 0 ; x < box_width ; x++) { + row[x] = (uint8_t) rand(); } } return std::shared_ptr(image); } - // // This is used to help rig up Monitor benchmarks. // -class TestMonitor : public Monitor -{ -public: - TestMonitor(int width, int height) { - cur_zone_id = 111; - +class TestMonitor : public Monitor { + public: + TestMonitor(int width, int height) : cur_zone_id(111) { this->width = width; this->height = height; // Create a dummy ref_image. std::shared_ptr tempImage = GenerateRandomImage(0, width, height); - ref_image = *tempImage.get(); + ref_image = *tempImage; shared_data = &temp_shared_data; } @@ -188,49 +180,44 @@ public: // // p_filter_box: The size of the filter to use. // - void AddZone(Zone::CheckMethod checkMethod, const Vector2 &p_filter_box=Vector2(5,5)) { - const int p_min_pixel_threshold = 50; - const int p_max_pixel_threshold = 255; - const int p_min_alarm_pixels = 1000; - const int p_max_alarm_pixels = 10000000; + void AddZone(Zone::CheckMethod checkMethod, const Vector2 &p_filter_box = Vector2(5, 5)) { + const int p_min_pixel_threshold = 50; + const int p_max_pixel_threshold = 255; + const int p_min_alarm_pixels = 1000; + const int p_max_alarm_pixels = 10000000; - const int zone_id = cur_zone_id++; - const std::string zone_label = std::string("zone_") + std::to_string(zone_id); - const Zone::ZoneType zoneType = Zone::ZoneType::ACTIVE; - const Polygon poly({ - Vector2(0, 0), - Vector2(width-1, 0), - Vector2(width-1, height-1), - Vector2(0, height-1)}); + const int zone_id = cur_zone_id++; + const std::string zone_label = std::string("zone_") + std::to_string(zone_id); + const Zone::ZoneType zone_type = Zone::ZoneType::ACTIVE; + const Polygon poly({Vector2(0, 0), + Vector2(width - 1, 0), + Vector2(width - 1, height - 1), + Vector2(0, height - 1)}); - Zone zone( - this, - zone_id, - zone_label.c_str(), - zoneType, - poly, - kRGBGreen, - Zone::CheckMethod::FILTERED_PIXELS, - p_min_pixel_threshold, - p_max_pixel_threshold, - p_min_alarm_pixels, - p_max_alarm_pixels, - p_filter_box - ); - zones.push_back(zone); + Zone zone(this, + zone_id, + zone_label.c_str(), + zone_type, + poly, + kRGBGreen, + Zone::CheckMethod::FILTERED_PIXELS, + p_min_pixel_threshold, + p_max_pixel_threshold, + p_min_alarm_pixels, + p_max_alarm_pixels, + p_filter_box); + zones.push_back(zone); } void SetRefImage(const Image *image) { ref_image = *image; } -private: + private: SharedData temp_shared_data; int cur_zone_id; }; - - // // Run zone benchmarks on the given image. // @@ -244,10 +231,9 @@ private: // Return: // The average time taken for each DetectMotion call. // -Microseconds RunDetectMotionBenchmark( - const std::string &label, - std::shared_ptr image, - const Vector2 &p_filter_box) { +Microseconds RunDetectMotionBenchmark(const std::string &label, + std::shared_ptr image, + const Vector2 &p_filter_box) { // Create a monitor to use for the benchmark. Give it 1 zone that uses // a 5x5 filter. TestMonitor testMonitor(image->Width(), image->Height()); @@ -255,29 +241,27 @@ Microseconds RunDetectMotionBenchmark( // Generate a black image to use as the reference image. std::shared_ptr blackImage = GenerateRandomImage( - 0, image->Width(), image->Height()); + 0, image->Width(), image->Height()); testMonitor.SetRefImage(blackImage.get()); Microseconds totalTimeTaken(0); // Run a series of passes over DetectMotion. const int numPasses = 10; - for (int i=0; i < numPasses; i++) - { - printf("\r%s - pass %2d / %2d ", label.c_str(), i+1, numPasses); + for (int i = 0 ; i < numPasses ; i++) { + printf("\r%s - pass %2d / %2d ", label.c_str(), i + 1, numPasses); fflush(stdout); TimeSegmentAdder adder(totalTimeTaken); Event::StringSet zoneSet; - testMonitor.DetectMotion(*image.get(), zoneSet); + testMonitor.DetectMotion(*image, zoneSet); } printf("\n"); return totalTimeTaken / numPasses; } - // // This runs a set of Monitor::DetectMotion benchmarks, one for each of the // "delta box percents" that are passed in. This adds one row to the @@ -293,45 +277,45 @@ Microseconds RunDetectMotionBenchmark( // void RunDetectMotionBenchmarks( TimingsTable &table, - const std::vector &deltaBoxPercents, + const std::vector &delta_box_percents, const Vector2 &p_filter_box) { std::vector timings; - for (int percent : deltaBoxPercents) { + for (int percent : delta_box_percents) { Microseconds timing = RunDetectMotionBenchmark( - std::string("DetectMotion: ") + std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + " box, " + std::to_string(percent) + "% delta", - GenerateRandomImage(percent), - p_filter_box); + std::string("DetectMotion: ") + std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + + " box, " + std::to_string(percent) + "% delta", + GenerateRandomImage(percent), + p_filter_box); timings.push_back(timing); } table.AddRow( - std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + " filter", - timings); + std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + " filter", + timings); } - int main(int argc, char *argv[]) { srand(111); // Init global stuff that we need. config.font_file_location = "../fonts/default.zmfnt"; config.event_close_mode = "time"; - config.cpu_extensions = 1; + config.cpu_extensions = true; // Detect SSE version. HwCapsDetect(); - + // Setup the column titles for the TimingsTable we'll generate. // Each column represents how large the box in the image is with delta pixels. // Each row represents a different filter size. const std::vector percents = {0, 10, 50, 100}; std::vector columns(percents.size()); std::transform(percents.begin(), percents.end(), columns.begin(), - [](const int percent) {return std::to_string(percent) + "% delta (ms)";}); + [](const int percent) { return std::to_string(percent) + "% delta (ms)"; }); TimingsTable table(columns); - std::vector filterSizes = {Vector2(3,3), Vector2(5,5), Vector2(13,13)}; + std::vector filterSizes = {Vector2(3, 3), Vector2(5, 5), Vector2(13, 13)}; for (const auto filterSize : filterSizes) { RunDetectMotionBenchmarks(table, percents, filterSize); } From c027b7a38d3637af69423a513b14f97c098ecd24 Mon Sep 17 00:00:00 2001 From: Peter Keresztes Schmidt Date: Tue, 12 Oct 2021 19:05:22 +0200 Subject: [PATCH 09/10] Fix some clang-tidy warnings --- src/zmbenchmark.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/zmbenchmark.cpp b/src/zmbenchmark.cpp index 2e9bd7678..926e073a7 100644 --- a/src/zmbenchmark.cpp +++ b/src/zmbenchmark.cpp @@ -60,7 +60,7 @@ class TimingsTable { // void Print(const int column_pad = 5) { // Figure out column widths. - std::vector widths(columns_.size() + 1); + std::vector widths(columns_.size() + 1); // The first width is the max of the row labels. auto result = std::max_element(rows_.begin(), @@ -91,8 +91,7 @@ class TimingsTable { PrintColStr(0, row.label); for (size_t i = 0 ; i < row.timings.size() ; i++) { - char num[128]; - sprintf(num, "%.2f", row.timings[i].count() / 1000.0); + std::string num = stringtf("%.2f", std::chrono::duration_cast(row.timings[i]).count()); PrintColStr(i + 1, num); } @@ -101,7 +100,7 @@ class TimingsTable { } private: - static void PrintPadding(int count) { + static void PrintPadding(size_t count) { std::string str(count, ' '); printf("%s", str.c_str()); } @@ -232,7 +231,7 @@ class TestMonitor : public Monitor { // The average time taken for each DetectMotion call. // Microseconds RunDetectMotionBenchmark(const std::string &label, - std::shared_ptr image, + const std::shared_ptr& image, const Vector2 &p_filter_box) { // Create a monitor to use for the benchmark. Give it 1 zone that uses // a 5x5 filter. From cd45c6155519798d03111b4ebef806dd0aa6f9ff Mon Sep 17 00:00:00 2001 From: Peter Keresztes Schmidt Date: Tue, 12 Oct 2021 19:18:21 +0200 Subject: [PATCH 10/10] Use mt19937 as PRNG Reasons: It's faster than rand() and can be made thread-safe --- src/zmbenchmark.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/zmbenchmark.cpp b/src/zmbenchmark.cpp index 926e073a7..244094a54 100644 --- a/src/zmbenchmark.cpp +++ b/src/zmbenchmark.cpp @@ -19,8 +19,9 @@ #include #include -#include #include +#include +#include #include #include "zm_config.h" @@ -30,6 +31,8 @@ #include "zm_utils.h" #include "zm_zone.h" +static std::mt19937 mt_rand(111); + // // This allows you to feed in a set of columns and timing rows, and print it // out in a nice-looking table. @@ -142,13 +145,13 @@ std::shared_ptr GenerateRandomImage( // Now randomize the pixels inside a box. const int box_width = (width * change_box_percent) / 100; const int box_height = (height * change_box_percent) / 100; - const int box_x = (int) ((uint64_t) rand() * (width - box_width) / RAND_MAX); - const int box_y = (int) ((uint64_t) rand() * (height - box_height) / RAND_MAX); + const int box_x = (int) ((uint64_t) mt_rand() * (width - box_width) / RAND_MAX); + const int box_y = (int) ((uint64_t) mt_rand() * (height - box_height) / RAND_MAX); for (int y = 0 ; y < box_height ; y++) { uint8_t *row = (uint8_t *) image->Buffer(box_x, box_y + y); for (int x = 0 ; x < box_width ; x++) { - row[x] = (uint8_t) rand(); + row[x] = (uint8_t) mt_rand(); } } @@ -295,8 +298,6 @@ void RunDetectMotionBenchmarks( } int main(int argc, char *argv[]) { - srand(111); - // Init global stuff that we need. config.font_file_location = "../fonts/default.zmfnt"; config.event_close_mode = "time";