mirror of
https://github.com/opencv/opencv.git
synced 2026-07-29 15:23:05 +04:00
Merge pull request #24845 from TolyaTalamanov:at/concurrent-executor
G-API: Implement concurrent executor #24845 ## Overview This PR introduces the new G-API executor called `GThreadedExecutor` which can be selected when the `GComputation` is compiled in `serial` mode (a.k.a `GComputation::compile(...)`) ### ThreadPool `cv::gapi::own::ThreadPool` has been introduced in order to abstract usage of threads in `GThreadedExecutor`. `ThreadPool` is implemented by using `own::concurrent_bounded_queue` `ThreadPool` has only as single method `schedule` that will push task into the queue for the further execution. The **important** notice is that if `Task` executed in `ThreadPool` throws exception - this is `UB`. ### GThreadedExecutor The `GThreadedExecutor` is mostly copy-paste of `GExecutor`, should we extend `GExecutor` instead? #### Implementation details 1. Build the dependency graph for `Island` nodes. 2. Store the tasks that don't have dependencies into separate `vector` in order to run them first. 3. at the `GThreadedExecutor::run()` schedule the tasks that don't have dependencies that will schedule their dependents and wait for the completion. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [ ] I agree to contribute to the project under Apache 2 License. - [ ] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [ ] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
committed by
GitHub
parent
87f749277d
commit
8e43c8f200
@@ -13,6 +13,8 @@
|
||||
|
||||
#include <opencv2/gapi/core.hpp>
|
||||
|
||||
#include "executor/thread_pool.hpp"
|
||||
|
||||
namespace opencv_test
|
||||
{
|
||||
|
||||
@@ -67,6 +69,38 @@ namespace
|
||||
}
|
||||
};
|
||||
|
||||
G_TYPED_KERNEL(GBusyWait, <GMat(GMat, uint32_t)>, "org.busy_wait") {
|
||||
static GMatDesc outMeta(GMatDesc in, uint32_t)
|
||||
{
|
||||
return in;
|
||||
}
|
||||
};
|
||||
|
||||
GAPI_OCV_KERNEL(GOCVBusyWait, GBusyWait)
|
||||
{
|
||||
static void run(const cv::Mat& in,
|
||||
const uint32_t time_in_ms,
|
||||
cv::Mat& out)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto s = high_resolution_clock::now();
|
||||
in.copyTo(out);
|
||||
auto e = high_resolution_clock::now();
|
||||
|
||||
const auto elapsed_in_ms =
|
||||
static_cast<int32_t>(duration_cast<milliseconds>(e-s).count());
|
||||
|
||||
int32_t diff = time_in_ms - elapsed_in_ms;
|
||||
const auto need_to_wait_in_ms = static_cast<uint32_t>(std::max(0, diff));
|
||||
|
||||
s = high_resolution_clock::now();
|
||||
e = s;
|
||||
while (duration_cast<milliseconds>(e-s).count() < need_to_wait_in_ms) {
|
||||
e = high_resolution_clock::now();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// These definitions test the correct macro work if the kernel has multiple output values
|
||||
G_TYPED_KERNEL(GRetGArrayTupleOfGMat2Kernel, <GArray<std::tuple<GMat, GMat>>(GMat, Scalar)>, "org.opencv.test.retarrayoftupleofgmat2kernel") {};
|
||||
G_TYPED_KERNEL(GRetGArraTupleyOfGMat3Kernel, <GArray<std::tuple<GMat, GMat, GMat>>(GMat)>, "org.opencv.test.retarrayoftupleofgmat3kernel") {};
|
||||
@@ -513,4 +547,29 @@ TEST(GAPI_Pipeline, 1DMatWithinSingleIsland)
|
||||
EXPECT_EQ(0, cv::norm(out_mat, ref_mat));
|
||||
}
|
||||
|
||||
TEST(GAPI_Pipeline, BranchesExecutedInParallel)
|
||||
{
|
||||
cv::GMat in;
|
||||
// NB: cv::gapi::copy used to prevent fusing OCV backend operations
|
||||
// into the single island where they will be executed in turn
|
||||
auto out0 = GBusyWait::on(cv::gapi::copy(in), 1000u /*1sec*/);
|
||||
auto out1 = GBusyWait::on(cv::gapi::copy(in), 1000u /*1sec*/);
|
||||
auto out2 = GBusyWait::on(cv::gapi::copy(in), 1000u /*1sec*/);
|
||||
auto out3 = GBusyWait::on(cv::gapi::copy(in), 1000u /*1sec*/);
|
||||
|
||||
cv::GComputation comp(cv::GIn(in), cv::GOut(out0,out1,out2,out3));
|
||||
cv::Mat in_mat = cv::Mat::eye(32, 32, CV_8UC1);
|
||||
cv::Mat out_mat0, out_mat1, out_mat2, out_mat3;
|
||||
|
||||
using namespace std::chrono;
|
||||
auto s = high_resolution_clock::now();
|
||||
comp.apply(cv::gin(in_mat), cv::gout(out_mat0, out_mat1, out_mat2, out_mat3),
|
||||
cv::compile_args(cv::use_threaded_executor(4u),
|
||||
cv::gapi::kernels<GOCVBusyWait>()));
|
||||
auto e = high_resolution_clock::now();
|
||||
const auto elapsed_in_ms = duration_cast<milliseconds>(e-s).count();;
|
||||
|
||||
EXPECT_GE(1200u, elapsed_in_ms);
|
||||
}
|
||||
|
||||
} // namespace opencv_test
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
// 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.
|
||||
//
|
||||
// Copyright (C) 2024 Intel Corporation
|
||||
|
||||
#include "../test_precomp.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "executor/thread_pool.hpp"
|
||||
|
||||
namespace opencv_test
|
||||
{
|
||||
|
||||
using namespace cv::gapi;
|
||||
|
||||
TEST(ThreadPool, ScheduleNotBlock)
|
||||
{
|
||||
own::Latch latch(1u);
|
||||
std::atomic<uint32_t> counter{0u};
|
||||
|
||||
own::ThreadPool tp(4u);
|
||||
tp.schedule([&](){
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{500u});
|
||||
counter++;
|
||||
latch.count_down();
|
||||
});
|
||||
|
||||
EXPECT_EQ(0u, counter);
|
||||
latch.wait();
|
||||
EXPECT_EQ(1u, counter);
|
||||
}
|
||||
|
||||
TEST(ThreadPool, MultipleTasks)
|
||||
{
|
||||
const uint32_t kNumTasks = 100u;
|
||||
own::Latch latch(kNumTasks);
|
||||
std::atomic<uint32_t> completed{0u};
|
||||
|
||||
own::ThreadPool tp(4u);
|
||||
for (uint32_t i = 0; i < kNumTasks; ++i) {
|
||||
tp.schedule([&]() {
|
||||
++completed;
|
||||
latch.count_down();
|
||||
});
|
||||
}
|
||||
latch.wait();
|
||||
|
||||
EXPECT_EQ(kNumTasks, completed.load());
|
||||
}
|
||||
|
||||
struct ExecutionState {
|
||||
ExecutionState(const uint32_t num_threads,
|
||||
const uint32_t num_tasks)
|
||||
: guard(0u),
|
||||
critical(0u),
|
||||
limit(num_tasks),
|
||||
latch(num_threads),
|
||||
tp(num_threads) {
|
||||
}
|
||||
|
||||
std::atomic<uint32_t> guard;
|
||||
std::atomic<uint32_t> critical;
|
||||
const uint32_t limit;
|
||||
own::Latch latch;
|
||||
own::ThreadPool tp;
|
||||
};
|
||||
|
||||
static void doRecursive(ExecutionState& state) {
|
||||
// NB: Protects function to be executed no more than limit number of times
|
||||
if (state.guard.fetch_add(1u) >= state.limit) {
|
||||
state.latch.count_down();
|
||||
return;
|
||||
}
|
||||
// NB: This simulates critical section
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{50});
|
||||
++state.critical;
|
||||
// NB: Schedule the new one recursively
|
||||
state.tp.schedule([&](){ doRecursive(state); });
|
||||
}
|
||||
|
||||
TEST(ThreadPool, ScheduleRecursively)
|
||||
{
|
||||
const int kNumThreads = 5u;
|
||||
const uint32_t kNumTasks = 100u;
|
||||
|
||||
ExecutionState state(kNumThreads, kNumTasks);
|
||||
for (uint32_t i = 0; i < kNumThreads; ++i) {
|
||||
state.tp.schedule([&](){
|
||||
doRecursive(state);
|
||||
});
|
||||
}
|
||||
state.latch.wait();
|
||||
|
||||
EXPECT_EQ(kNumTasks, state.critical.load());
|
||||
}
|
||||
|
||||
TEST(ThreadPool, ExecutionIsParallel)
|
||||
{
|
||||
const uint32_t kNumThreads = 4u;
|
||||
std::atomic<uint32_t> counter{0};
|
||||
own::Latch latch{kNumThreads};
|
||||
|
||||
own::ThreadPool tp(kNumThreads);
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
for (uint32_t i = 0; i < kNumThreads; ++i) {
|
||||
tp.schedule([&]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{800u});
|
||||
++counter;
|
||||
latch.count_down();
|
||||
});
|
||||
}
|
||||
latch.wait();
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||
|
||||
EXPECT_GE(1000u, elapsed);
|
||||
EXPECT_EQ(kNumThreads, counter.load());
|
||||
}
|
||||
|
||||
} // namespace opencv_test
|
||||
Reference in New Issue
Block a user