From b73bf03bfcc44f569dd777afefd544933cd136a8 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 21 Jan 2021 11:03:17 +0000 Subject: [PATCH] core: parallel backends API - allow to replace parallel_for() backend --- cmake/templates/opencv_abi.xml.in | 1 + doc/Doxyfile.in | 1 + modules/core/CMakeLists.txt | 7 +- modules/core/include/opencv2/core.hpp | 4 + .../parallel/backend/parallel_for.openmp.hpp | 72 +++++++++ .../parallel/backend/parallel_for.tbb.hpp | 153 ++++++++++++++++++ .../core/parallel/parallel_backend.hpp | 73 +++++++++ modules/core/include/opencv2/core/utility.hpp | 19 ++- modules/core/src/parallel.cpp | 105 +++++++++--- samples/cpp/CMakeLists.txt | 9 ++ .../core/parallel_backend/CMakeLists.txt | 26 +++ .../core/parallel_backend/example-openmp.cpp | 44 +++++ .../core/parallel_backend/example-tbb.cpp | 43 +++++ 13 files changed, 527 insertions(+), 30 deletions(-) create mode 100644 modules/core/include/opencv2/core/parallel/backend/parallel_for.openmp.hpp create mode 100644 modules/core/include/opencv2/core/parallel/backend/parallel_for.tbb.hpp create mode 100644 modules/core/include/opencv2/core/parallel/parallel_backend.hpp create mode 100644 samples/cpp/tutorial_code/core/parallel_backend/CMakeLists.txt create mode 100644 samples/cpp/tutorial_code/core/parallel_backend/example-openmp.cpp create mode 100644 samples/cpp/tutorial_code/core/parallel_backend/example-tbb.cpp diff --git a/cmake/templates/opencv_abi.xml.in b/cmake/templates/opencv_abi.xml.in index 365bdc49fa..614bbe4fb2 100644 --- a/cmake/templates/opencv_abi.xml.in +++ b/cmake/templates/opencv_abi.xml.in @@ -26,6 +26,7 @@ opencv2/core/hal/*.impl.* opencv2/core/cuda* opencv2/core/opencl* + opencv2/core/parallel/backend/* opencv2/core/private* opencv2/core/quaternion* opencv/cxeigen.hpp diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 7faa6f7d84..f5a998da3d 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -227,6 +227,7 @@ INCLUDE_PATH = INCLUDE_FILE_PATTERNS = PREDEFINED = __cplusplus=1 \ CVAPI(x)=x \ + CV_API_CALL= \ CV_DOXYGEN= \ CV_EXPORTS= \ CV_EXPORTS_W= \ diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index eba022d222..a4781fe8ac 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -58,10 +58,15 @@ file(GLOB_RECURSE module_opencl_hdrs source_group("Include\\Cuda Headers" FILES ${lib_cuda_hdrs}) source_group("Include\\Cuda Headers\\Detail" FILES ${lib_cuda_hdrs_detail}) +file(GLOB_RECURSE core_parallel_hdrs + "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/parallel/*.hpp" + "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/parallel/*.h") +ocv_source_group("Include" DIRBASE "${CMAKE_CURRENT_LIST_DIR}/include" FILES ${core_parallel_hdrs}) + source_group("Src" FILES "${OPENCV_MODULE_opencv_core_BINARY_DIR}/version_string.inc") ocv_glob_module_sources(SOURCES "${OPENCV_MODULE_opencv_core_BINARY_DIR}/version_string.inc" - HEADERS ${module_opencl_hdrs} ${lib_cuda_hdrs} ${lib_cuda_hdrs_detail}) + HEADERS ${core_parallel_hdrs} ${module_opencl_hdrs} ${lib_cuda_hdrs} ${lib_cuda_hdrs_detail}) ocv_module_include_directories(${the_module} ${ZLIB_INCLUDE_DIRS} ${OPENCL_INCLUDE_DIRS}) if(ANDROID AND HAVE_CPUFEATURES) diff --git a/modules/core/include/opencv2/core.hpp b/modules/core/include/opencv2/core.hpp index 50af505968..6ba209b397 100644 --- a/modules/core/include/opencv2/core.hpp +++ b/modules/core/include/opencv2/core.hpp @@ -97,6 +97,10 @@ @} @defgroup core_lowlevel_api Low-level API for external libraries / plugins @} + @defgroup core_parallel Parallel Processing + @{ + @defgroup core_parallel_backend Parallel backends API + @} @} */ diff --git a/modules/core/include/opencv2/core/parallel/backend/parallel_for.openmp.hpp b/modules/core/include/opencv2/core/parallel/backend/parallel_for.openmp.hpp new file mode 100644 index 0000000000..b172cac34d --- /dev/null +++ b/modules/core/include/opencv2/core/parallel/backend/parallel_for.openmp.hpp @@ -0,0 +1,72 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef OPENCV_CORE_PARALLEL_FOR_OPENMP_HPP +#define OPENCV_CORE_PARALLEL_FOR_OPENMP_HPP + +#include "opencv2/core/parallel/parallel_backend.hpp" + +#if !defined(_OPENMP) && !defined(OPENCV_SKIP_OPENMP_PRESENSE_CHECK) +#error "This file must be compiled with enabled OpenMP" +#endif + +#include + +namespace cv { namespace parallel { namespace openmp { + +/** OpenMP parallel_for API implementation + * + * @sa setParallelForBackend + * @ingroup core_parallel_backend + */ +class ParallelForBackend : public ParallelForAPI +{ +protected: + int numThreads; + int numThreadsMax; +public: + ParallelForBackend() + { + numThreads = 0; + numThreadsMax = omp_get_max_threads(); + } + + virtual ~ParallelForBackend() {} + + virtual void parallel_for(int tasks, FN_parallel_for_body_cb_t body_callback, void* callback_data) CV_OVERRIDE + { +#pragma omp parallel for schedule(dynamic) num_threads(numThreads > 0 ? numThreads : numThreadsMax) + for (int i = 0; i < tasks; ++i) + body_callback(i, i + 1, callback_data); + } + + virtual int getThreadNum() const CV_OVERRIDE + { + return omp_get_thread_num(); + } + + virtual int getNumThreads() const CV_OVERRIDE + { + return numThreads > 0 + ? numThreads + : numThreadsMax; + } + + virtual int setNumThreads(int nThreads) CV_OVERRIDE + { + int oldNumThreads = numThreads; + numThreads = nThreads; + // nothing needed as numThreads is used in #pragma omp parallel for directly + return oldNumThreads; + } + + const char* getName() const CV_OVERRIDE + { + return "openmp"; + } +}; + +}}} // namespace + +#endif // OPENCV_CORE_PARALLEL_FOR_OPENMP_HPP diff --git a/modules/core/include/opencv2/core/parallel/backend/parallel_for.tbb.hpp b/modules/core/include/opencv2/core/parallel/backend/parallel_for.tbb.hpp new file mode 100644 index 0000000000..264def5f50 --- /dev/null +++ b/modules/core/include/opencv2/core/parallel/backend/parallel_for.tbb.hpp @@ -0,0 +1,153 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef OPENCV_CORE_PARALLEL_FOR_TBB_HPP +#define OPENCV_CORE_PARALLEL_FOR_TBB_HPP + +#include "opencv2/core/parallel/parallel_backend.hpp" +#include + +#ifndef TBB_SUPPRESS_DEPRECATED_MESSAGES // supress warning +#define TBB_SUPPRESS_DEPRECATED_MESSAGES 1 +#endif +#include "tbb/tbb.h" +#if !defined(TBB_INTERFACE_VERSION) +#error "Unknows/unsupported TBB version" +#endif + +#if TBB_INTERFACE_VERSION >= 8000 +#include "tbb/task_arena.h" +#endif + +namespace cv { namespace parallel { namespace tbb { + +using namespace ::tbb; + +#if TBB_INTERFACE_VERSION >= 8000 +static tbb::task_arena& getArena() +{ + static tbb::task_arena tbbArena(tbb::task_arena::automatic); + return tbbArena; +} +#else +static tbb::task_scheduler_init& getScheduler() +{ + static tbb::task_scheduler_init tbbScheduler(tbb::task_scheduler_init::deferred); + return tbbScheduler; +} +#endif + +/** OpenMP parallel_for API implementation + * + * @sa setParallelForBackend + * @ingroup core_parallel_backend + */ +class ParallelForBackend : public ParallelForAPI +{ +protected: + int numThreads; + int numThreadsMax; +public: + ParallelForBackend() + { + CV_LOG_INFO(NULL, "Initializing TBB parallel backend: TBB_INTERFACE_VERSION=" << TBB_INTERFACE_VERSION); + numThreads = 0; +#if TBB_INTERFACE_VERSION >= 8000 + (void)getArena(); +#else + (void)getScheduler(); +#endif + } + + virtual ~ParallelForBackend() {} + + class CallbackProxy + { + const FN_parallel_for_body_cb_t& callback; + void* const callback_data; + const int tasks; + public: + inline CallbackProxy(int tasks_, FN_parallel_for_body_cb_t& callback_, void* callback_data_) + : callback(callback_), callback_data(callback_data_), tasks(tasks_) + { + // nothing + } + + void operator()(const tbb::blocked_range& range) const + { + this->callback(range.begin(), range.end(), callback_data); + } + + void operator()() const + { + tbb::parallel_for(tbb::blocked_range(0, tasks), *this); + } + }; + + virtual void parallel_for(int tasks, FN_parallel_for_body_cb_t body_callback, void* callback_data) CV_OVERRIDE + { + CallbackProxy task(tasks, body_callback, callback_data); +#if TBB_INTERFACE_VERSION >= 8000 + getArena().execute(task); +#else + task(); +#endif + } + + virtual int getThreadNum() const CV_OVERRIDE + { +#if TBB_INTERFACE_VERSION >= 9100 + return tbb::this_task_arena::current_thread_index(); +#elif TBB_INTERFACE_VERSION >= 8000 + return tbb::task_arena::current_thread_index(); +#else + return 0; +#endif + } + + virtual int getNumThreads() const CV_OVERRIDE + { +#if TBB_INTERFACE_VERSION >= 9100 + return getArena().max_concurrency(); +#elif TBB_INTERFACE_VERSION >= 8000 + return numThreads > 0 + ? numThreads + : tbb::task_scheduler_init::default_num_threads(); +#else + return getScheduler().is_active() + ? numThreads + : tbb::task_scheduler_init::default_num_threads(); +#endif + } + + virtual int setNumThreads(int nThreads) CV_OVERRIDE + { + int oldNumThreads = numThreads; + numThreads = nThreads; + +#if TBB_INTERFACE_VERSION >= 8000 + auto& tbbArena = getArena(); + if (tbbArena.is_active()) + tbbArena.terminate(); + if (numThreads > 0) + tbbArena.initialize(numThreads); +#else + auto& tbbScheduler = getScheduler(); + if (tbbScheduler.is_active()) + tbbScheduler.terminate(); + if (numThreads > 0) + tbbScheduler.initialize(numThreads); +#endif + return oldNumThreads; + } + + const char* getName() const CV_OVERRIDE + { + return "tbb"; + } +}; + +}}} // namespace + +#endif // OPENCV_CORE_PARALLEL_FOR_TBB_HPP diff --git a/modules/core/include/opencv2/core/parallel/parallel_backend.hpp b/modules/core/include/opencv2/core/parallel/parallel_backend.hpp new file mode 100644 index 0000000000..c519c3b27b --- /dev/null +++ b/modules/core/include/opencv2/core/parallel/parallel_backend.hpp @@ -0,0 +1,73 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef OPENCV_CORE_PARALLEL_BACKEND_HPP +#define OPENCV_CORE_PARALLEL_BACKEND_HPP + +#include + +namespace cv { namespace parallel { +#ifndef CV_API_CALL +#define CV_API_CALL +#endif + +/** @addtogroup core_parallel_backend + * @{ + * API below is provided to resolve problem of CPU resource over-subscription by multiple thread pools from different multi-threading frameworks. + * This is common problem for cases when OpenCV compiled threading framework is different from the Users Applications framework. + * + * Applications can replace OpenCV `parallel_for()` backend with own implementation (to reuse Application's thread pool). + * + * @note This call is not thread-safe. Consider calling this function from the `main()` before any other OpenCV processing functions (and without any other created threads). + * + * #### Intel TBB usage example: + * + * - include header with simple implementation of TBB backend: + * @snippet parallel_backend/example-tbb.cpp tbb_include + * - execute backend replacement code: + * @snippet parallel_backend/example-tbb.cpp tbb_backend + * - configuration of compiler/linker options is responsibility of Application's scripts + * + * #### OpenMP usage example: + * + * - include header with simple implementation of OpenMP backend: + * @snippet parallel_backend/example-openmp.cpp openmp_include + * - execute backend replacement code: + * @snippet parallel_backend/example-openmp.cpp openmp_backend + * - Configuration of compiler/linker options is responsibility of Application's scripts + */ + +/** Interface for parallel_for backends implementations + * + * @sa setParallelForBackend + */ +class CV_EXPORTS ParallelForAPI +{ +public: + virtual ~ParallelForAPI(); + + typedef void (CV_API_CALL *FN_parallel_for_body_cb_t)(int start, int end, void* data); + + virtual void parallel_for(int tasks, FN_parallel_for_body_cb_t body_callback, void* callback_data) = 0; + + virtual int getThreadNum() const = 0; + + virtual int getNumThreads() const = 0; + + virtual int setNumThreads(int nThreads) = 0; + + virtual const char* getName() const = 0; +}; + +/** @brief Replace OpenCV parallel_for backend + * + * Application can replace OpenCV `parallel_for()` backend with own implementation. + * + * @note This call is not thread-safe. Consider calling this function from the `main()` before any other OpenCV processing functions (and without any other created threads). + */ +CV_EXPORTS void setParallelForBackend(const std::shared_ptr& api, bool propagateNumThreads = true); + +//! @} +}} // namespace +#endif // OPENCV_CORE_PARALLEL_BACKEND_HPP diff --git a/modules/core/include/opencv2/core/utility.hpp b/modules/core/include/opencv2/core/utility.hpp index c52abbbff4..f0368027aa 100644 --- a/modules/core/include/opencv2/core/utility.hpp +++ b/modules/core/include/opencv2/core/utility.hpp @@ -570,6 +570,8 @@ static inline size_t getElemSize(int type) { return (size_t)CV_ELEM_SIZE(type); /////////////////////////////// Parallel Primitives ////////////////////////////////// /** @brief Base class for parallel data processors + +@ingroup core_parallel */ class CV_EXPORTS ParallelLoopBody { @@ -579,17 +581,23 @@ public: }; /** @brief Parallel data processor + +@ingroup core_parallel */ CV_EXPORTS void parallel_for_(const Range& range, const ParallelLoopBody& body, double nstripes=-1.); +//! @ingroup core_parallel class ParallelLoopBodyLambdaWrapper : public ParallelLoopBody { private: std::function m_functor; public: - ParallelLoopBodyLambdaWrapper(std::function functor) : - m_functor(functor) - { } + inline + ParallelLoopBodyLambdaWrapper(std::function functor) + : m_functor(functor) + { + // nothing + } virtual void operator() (const cv::Range& range) const CV_OVERRIDE { @@ -597,11 +605,14 @@ public: } }; -inline void parallel_for_(const Range& range, std::function functor, double nstripes=-1.) +//! @ingroup core_parallel +static inline +void parallel_for_(const Range& range, std::function functor, double nstripes=-1.) { parallel_for_(range, ParallelLoopBodyLambdaWrapper(functor), nstripes); } + /////////////////////////////// forEach method of cv::Mat //////////////////////////// template inline void Mat::forEach_impl(const Functor& operation) { diff --git a/modules/core/src/parallel.cpp b/modules/core/src/parallel.cpp index b4043b0b63..9432b225d3 100644 --- a/modules/core/src/parallel.cpp +++ b/modules/core/src/parallel.cpp @@ -45,6 +45,8 @@ #include #include +#include "opencv2/core/parallel/parallel_backend.hpp" + #if defined _WIN32 || defined WINCE #include #undef small @@ -145,9 +147,7 @@ # define CV_PARALLEL_FRAMEWORK "pthreads" #endif -#ifdef CV_PARALLEL_FRAMEWORK #include -#endif #include "parallel_impl.hpp" @@ -159,9 +159,36 @@ namespace cv { ParallelLoopBody::~ParallelLoopBody() {} +namespace parallel { + +static int numThreads = -1; + +static +std::shared_ptr& getCurrentParallelForAPI() +{ + static std::shared_ptr g_currentParallelForAPI; + return g_currentParallelForAPI; +} + +ParallelForAPI::~ParallelForAPI() +{ + // nothing +} + +void setParallelForBackend(const std::shared_ptr& api, bool propagateNumThreads) +{ + getCurrentParallelForAPI() = api; + if (propagateNumThreads && api) + { + setNumThreads(numThreads); + } +} + +} // namespace +using namespace cv::parallel; + namespace { -#ifdef CV_PARALLEL_FRAMEWORK #ifdef ENABLE_INSTRUMENTATION static void SyncNodes(cv::instr::InstrNode *pNode) { @@ -430,8 +457,6 @@ namespace { typedef ParallelLoopBodyWrapper ProxyLoopBody; #endif -static int numThreads = -1; - #if defined HAVE_TBB #if TBB_INTERFACE_VERSION >= 8000 static tbb::task_arena tbbArena(tbb::task_arena::automatic); @@ -446,7 +471,7 @@ static inline int _initMaxThreads() int maxThreads = omp_get_max_threads(); if (!utils::getConfigurationParameterBool("OPENCV_FOR_OPENMP_DYNAMIC_DISABLE", false)) { - omp_set_dynamic(maxThreads); + omp_set_dynamic(1); } return maxThreads; } @@ -477,15 +502,11 @@ static SchedPtr pplScheduler; #endif -#endif // CV_PARALLEL_FRAMEWORK - } // namespace anon /* ================================ parallel_for_ ================================ */ -#ifdef CV_PARALLEL_FRAMEWORK static void parallel_for_impl(const cv::Range& range, const cv::ParallelLoopBody& body, double nstripes); // forward declaration -#endif void parallel_for_(const cv::Range& range, const cv::ParallelLoopBody& body, double nstripes) { @@ -500,7 +521,6 @@ void parallel_for_(const cv::Range& range, const cv::ParallelLoopBody& body, dou if (range.empty()) return; -#ifdef CV_PARALLEL_FRAMEWORK static std::atomic flagNestedParallelFor(false); bool isNotNestedRegion = !flagNestedParallelFor.load(); if (isNotNestedRegion) @@ -519,16 +539,23 @@ void parallel_for_(const cv::Range& range, const cv::ParallelLoopBody& body, dou } } else // nested parallel_for_() calls are not parallelized -#endif // CV_PARALLEL_FRAMEWORK { CV_UNUSED(nstripes); body(range); } } -#ifdef CV_PARALLEL_FRAMEWORK +static +void parallel_for_cb(int start, int end, void* data) +{ + CV_DbgAssert(data); + const cv::ParallelLoopBody& body = *(const cv::ParallelLoopBody*)data; + body(Range(start, end)); +} + static void parallel_for_impl(const cv::Range& range, const cv::ParallelLoopBody& body, double nstripes) { + using namespace cv::parallel; if ((numThreads < 0 || numThreads > 1) && range.end - range.start > 1) { ParallelLoopBodyWrapperContext ctx(body, range, nstripes); @@ -540,6 +567,16 @@ static void parallel_for_impl(const cv::Range& range, const cv::ParallelLoopBody return; } + std::shared_ptr& api = getCurrentParallelForAPI(); + if (api) + { + CV_CheckEQ(stripeRange.start, 0, ""); + api->parallel_for(stripeRange.end, parallel_for_cb, (void*)&pbody); + ctx.finalize(); // propagate exceptions if exists + return; + } + +#ifdef CV_PARALLEL_FRAMEWORK #if defined HAVE_TBB #if TBB_INTERFACE_VERSION >= 8000 @@ -590,24 +627,25 @@ static void parallel_for_impl(const cv::Range& range, const cv::ParallelLoopBody #endif ctx.finalize(); // propagate exceptions if exists + return; +#endif // CV_PARALLEL_FRAMEWORK } - else - { - body(range); - } + + body(range); } -#endif // CV_PARALLEL_FRAMEWORK int getNumThreads(void) { -#ifdef CV_PARALLEL_FRAMEWORK + std::shared_ptr& api = getCurrentParallelForAPI(); + if (api) + { + return api->getNumThreads(); + } - if(numThreads == 0) + if (numThreads == 0) return 1; -#endif - #if defined HAVE_TBB #if TBB_INTERFACE_VERSION >= 9100 @@ -682,10 +720,15 @@ unsigned defaultNumberOfThreads() void setNumThreads( int threads_ ) { CV_UNUSED(threads_); -#ifdef CV_PARALLEL_FRAMEWORK + int threads = (threads_ < 0) ? defaultNumberOfThreads() : (unsigned)threads_; numThreads = threads; -#endif + + std::shared_ptr& api = getCurrentParallelForAPI(); + if (api) + { + api->setNumThreads(numThreads); + } #ifdef HAVE_TBB @@ -741,6 +784,12 @@ void setNumThreads( int threads_ ) int getThreadNum() { + std::shared_ptr& api = getCurrentParallelForAPI(); + if (api) + { + return api->getThreadNum(); + } + #if defined HAVE_TBB #if TBB_INTERFACE_VERSION >= 9100 return tbb::this_task_arena::current_thread_index(); @@ -963,7 +1012,13 @@ int getNumberOfCPUs() return nCPUs; // cached value } -const char* currentParallelFramework() { +const char* currentParallelFramework() +{ + std::shared_ptr& api = getCurrentParallelForAPI(); + if (api) + { + return api->getName(); + } #ifdef CV_PARALLEL_FRAMEWORK return CV_PARALLEL_FRAMEWORK; #else diff --git a/samples/cpp/CMakeLists.txt b/samples/cpp/CMakeLists.txt index 14ab6141df..4f8977b7bd 100644 --- a/samples/cpp/CMakeLists.txt +++ b/samples/cpp/CMakeLists.txt @@ -30,6 +30,7 @@ if(NOT HAVE_opencv_cudaarithm OR NOT HAVE_opencv_cudafilters) ocv_list_filterout(cpp_samples "/gpu/") endif() ocv_list_filterout(cpp_samples "real_time_pose_estimation/") +ocv_list_filterout(cpp_samples "parallel_backend/") foreach(sample_filename ${cpp_samples}) set(package "cpp") if(sample_filename MATCHES "tutorial_code") @@ -57,3 +58,11 @@ foreach(sample_filename ${cpp_samples}) endforeach() include("tutorial_code/calib3d/real_time_pose_estimation/CMakeLists.txt" OPTIONAL) + +# Standalone samples only +if(OpenCV_FOUND AND NOT CMAKE_VERSION VERSION_LESS "3.1") + add_subdirectory("example_cmake") +endif() +if(OpenCV_FOUND AND NOT CMAKE_VERSION VERSION_LESS "3.9") + add_subdirectory("tutorial_code/core/parallel_backend") +endif() diff --git a/samples/cpp/tutorial_code/core/parallel_backend/CMakeLists.txt b/samples/cpp/tutorial_code/core/parallel_backend/CMakeLists.txt new file mode 100644 index 0000000000..0e67dc29e2 --- /dev/null +++ b/samples/cpp/tutorial_code/core/parallel_backend/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.9) + +find_package(OpenCV REQUIRED COMPONENTS opencv_core) + +find_package(OpenMP) +if(OpenMP_FOUND) + project(opencv_example_openmp_backend) + add_executable(opencv_example_openmp_backend example-openmp.cpp) + target_link_libraries(opencv_example_openmp_backend PRIVATE + opencv_core + OpenMP::OpenMP_CXX + ) +endif() + +# TODO: find_package(TBB) +find_path(TBB_INCLUDE_DIR NAMES "tbb/tbb.h") +find_library(TBB_LIBRARY NAMES "tbb") +if(TBB_INCLUDE_DIR AND TBB_LIBRARY AND NOT OPENCV_EXAMPLE_SKIP_TBB) + project(opencv_example_tbb_backend) + add_executable(opencv_example_tbb_backend example-tbb.cpp) + target_include_directories(opencv_example_tbb_backend SYSTEM PRIVATE ${TBB_INCLUDE_DIR}) + target_link_libraries(opencv_example_tbb_backend PRIVATE + opencv_core + ${TBB_LIBRARY} + ) +endif() diff --git a/samples/cpp/tutorial_code/core/parallel_backend/example-openmp.cpp b/samples/cpp/tutorial_code/core/parallel_backend/example-openmp.cpp new file mode 100644 index 0000000000..aa3f143f7b --- /dev/null +++ b/samples/cpp/tutorial_code/core/parallel_backend/example-openmp.cpp @@ -0,0 +1,44 @@ +#include "opencv2/core.hpp" +#include + +#include +#include + +//! [openmp_include] +#include "opencv2/core/parallel/backend/parallel_for.openmp.hpp" +//! [openmp_include] + +namespace cv { // private.hpp +CV_EXPORTS const char* currentParallelFramework(); +} + +static +std::string currentParallelFrameworkSafe() +{ + const char* framework = cv::currentParallelFramework(); + if (framework) + return framework; + return std::string(); +} + +using namespace cv; +int main() +{ + std::cout << "OpenCV builtin parallel framework: '" << currentParallelFrameworkSafe() << "' (nthreads=" << getNumThreads() << ")" << std::endl; + + //! [openmp_backend] + //omp_set_dynamic(1); + cv::parallel::setParallelForBackend(std::make_shared()); + //! [openmp_backend] + + std::cout << "New parallel backend: '" << currentParallelFrameworkSafe() << "'" << "' (nthreads=" << getNumThreads() << ")" << std::endl; + + parallel_for_(Range(0, 20), [&](const Range range) + { + std::ostringstream out; + out << "Thread " << getThreadNum() << "(opencv=" << utils::getThreadID() << "): range " << range.start << "-" << range.end << std::endl; + std::cout << out.str() << std::flush; + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + }); +} diff --git a/samples/cpp/tutorial_code/core/parallel_backend/example-tbb.cpp b/samples/cpp/tutorial_code/core/parallel_backend/example-tbb.cpp new file mode 100644 index 0000000000..ebf0d260c1 --- /dev/null +++ b/samples/cpp/tutorial_code/core/parallel_backend/example-tbb.cpp @@ -0,0 +1,43 @@ +#include "opencv2/core.hpp" +#include + +#include +#include + +//! [tbb_include] +#include "opencv2/core/parallel/backend/parallel_for.tbb.hpp" +//! [tbb_include] + +namespace cv { // private.hpp +CV_EXPORTS const char* currentParallelFramework(); +} + +static +std::string currentParallelFrameworkSafe() +{ + const char* framework = cv::currentParallelFramework(); + if (framework) + return framework; + return std::string(); +} + +using namespace cv; +int main() +{ + std::cout << "OpenCV builtin parallel framework: '" << currentParallelFrameworkSafe() << "' (nthreads=" << getNumThreads() << ")" << std::endl; + + //! [tbb_backend] + cv::parallel::setParallelForBackend(std::make_shared()); + //! [tbb_backend] + + std::cout << "New parallel backend: '" << currentParallelFrameworkSafe() << "'" << "' (nthreads=" << getNumThreads() << ")" << std::endl; + + parallel_for_(Range(0, 20), [&](const Range range) + { + std::ostringstream out; + out << "Thread " << getThreadNum() << "(opencv=" << utils::getThreadID() << "): range " << range.start << "-" << range.end << std::endl; + std::cout << out.str() << std::flush; + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + }); +}