mirror of https://github.com/opencv/opencv.git
Open Source Computer Vision Library
https://opencv.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
349 lines
9.3 KiB
349 lines
9.3 KiB
3 years ago
|
// 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) 2021 Intel Corporation
|
||
|
|
||
|
|
||
|
#include "../test_precomp.hpp"
|
||
|
|
||
|
#include "../common/gapi_streaming_tests_common.hpp"
|
||
|
|
||
|
#include <chrono>
|
||
|
#include <future>
|
||
|
|
||
|
#define private public
|
||
|
#include "streaming/onevpl/accelerators/utils/shared_lock.hpp"
|
||
|
#undef private
|
||
|
|
||
|
#include "streaming/onevpl/accelerators/utils/elastic_barrier.hpp"
|
||
|
|
||
|
namespace opencv_test
|
||
|
{
|
||
|
namespace
|
||
|
{
|
||
|
using cv::gapi::wip::onevpl::SharedLock;
|
||
|
|
||
|
struct TestBarrier : public cv::gapi::wip::onevpl::elastic_barrier<TestBarrier> {
|
||
|
void on_first_in_impl(size_t visitor_id) {
|
||
|
|
||
|
static std::atomic<int> thread_counter{};
|
||
|
thread_counter++;
|
||
|
EXPECT_EQ(thread_counter.load(), 1);
|
||
|
|
||
|
visitors_in.insert(visitor_id);
|
||
|
last_visitor_id = visitor_id;
|
||
|
|
||
|
thread_counter--;
|
||
|
EXPECT_EQ(thread_counter.load(), 0);
|
||
|
}
|
||
|
|
||
|
void on_last_out_impl(size_t visitor_id) {
|
||
|
|
||
|
static std::atomic<int> thread_counter{};
|
||
|
thread_counter++;
|
||
|
EXPECT_EQ(thread_counter.load(), 1);
|
||
|
|
||
|
visitors_out.insert(visitor_id);
|
||
|
last_visitor_id = visitor_id;
|
||
|
|
||
|
thread_counter--;
|
||
|
EXPECT_EQ(thread_counter.load(), 0);
|
||
|
}
|
||
|
|
||
|
size_t last_visitor_id = 0;
|
||
|
std::set<size_t> visitors_in;
|
||
|
std::set<size_t> visitors_out;
|
||
|
};
|
||
|
|
||
|
TEST(OneVPL_SharedLock, Create) {
|
||
|
SharedLock lock;
|
||
|
EXPECT_EQ(lock.shared_counter.load(), size_t{0});
|
||
|
}
|
||
|
|
||
|
TEST(OneVPL_SharedLock, Read_SingleThread)
|
||
|
{
|
||
|
SharedLock lock;
|
||
|
|
||
|
const size_t single_thread_read_count = 100;
|
||
|
for(size_t i = 0; i < single_thread_read_count; i++) {
|
||
|
lock.shared_lock();
|
||
|
EXPECT_FALSE(lock.owns());
|
||
|
}
|
||
|
EXPECT_EQ(lock.shared_counter.load(), single_thread_read_count);
|
||
|
|
||
|
for(size_t i = 0; i < single_thread_read_count; i++) {
|
||
|
lock.unlock_shared();
|
||
|
EXPECT_FALSE(lock.owns());
|
||
|
}
|
||
|
|
||
|
EXPECT_EQ(lock.shared_counter.load(), size_t{0});
|
||
|
}
|
||
|
|
||
|
TEST(OneVPL_SharedLock, TryLock_SingleThread)
|
||
|
{
|
||
|
SharedLock lock;
|
||
|
|
||
|
EXPECT_TRUE(lock.try_lock());
|
||
|
EXPECT_TRUE(lock.owns());
|
||
|
|
||
|
lock.unlock();
|
||
|
EXPECT_FALSE(lock.owns());
|
||
|
EXPECT_EQ(lock.shared_counter.load(), size_t{0});
|
||
|
}
|
||
|
|
||
|
TEST(OneVPL_SharedLock, Write_SingleThread)
|
||
|
{
|
||
|
SharedLock lock;
|
||
|
|
||
|
lock.lock();
|
||
|
EXPECT_TRUE(lock.owns());
|
||
|
|
||
|
lock.unlock();
|
||
|
EXPECT_FALSE(lock.owns());
|
||
|
EXPECT_EQ(lock.shared_counter.load(), size_t{0});
|
||
|
}
|
||
|
|
||
|
TEST(OneVPL_SharedLock, TryLockTryLock_SingleThread)
|
||
|
{
|
||
|
SharedLock lock;
|
||
|
|
||
|
lock.try_lock();
|
||
|
EXPECT_FALSE(lock.try_lock());
|
||
|
lock.unlock();
|
||
|
|
||
|
EXPECT_FALSE(lock.owns());
|
||
|
}
|
||
|
|
||
|
TEST(OneVPL_SharedLock, ReadTryLock_SingleThread)
|
||
|
{
|
||
|
SharedLock lock;
|
||
|
|
||
|
lock.shared_lock();
|
||
|
EXPECT_FALSE(lock.owns());
|
||
|
EXPECT_FALSE(lock.try_lock());
|
||
|
lock.unlock_shared();
|
||
|
|
||
|
EXPECT_TRUE(lock.try_lock());
|
||
|
EXPECT_TRUE(lock.owns());
|
||
|
lock.unlock();
|
||
|
}
|
||
|
|
||
|
TEST(OneVPL_SharedLock, WriteTryLock_SingleThread)
|
||
|
{
|
||
|
SharedLock lock;
|
||
|
|
||
|
lock.lock();
|
||
|
EXPECT_TRUE(lock.owns());
|
||
|
EXPECT_FALSE(lock.try_lock());
|
||
|
lock.unlock();
|
||
|
|
||
|
EXPECT_TRUE(lock.try_lock());
|
||
|
EXPECT_TRUE(lock.owns());
|
||
|
lock.unlock();
|
||
|
}
|
||
|
|
||
|
|
||
|
TEST(OneVPL_SharedLock, Write_MultiThread)
|
||
|
{
|
||
|
SharedLock lock;
|
||
|
|
||
|
std::promise<void> barrier;
|
||
|
std::shared_future<void> sync = barrier.get_future();
|
||
|
|
||
|
static const size_t inc_count = 10000000;
|
||
|
size_t shared_value = 0;
|
||
|
auto work = [&lock, &shared_value](size_t count) {
|
||
|
for (size_t i = 0; i < count; i ++) {
|
||
|
lock.lock();
|
||
|
shared_value ++;
|
||
|
lock.unlock();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
std::thread worker_thread([&barrier, sync, work] () {
|
||
|
|
||
|
std::thread sub_worker([&barrier, work] () {
|
||
|
barrier.set_value();
|
||
|
work(inc_count);
|
||
|
});
|
||
|
|
||
|
sync.wait();
|
||
|
work(inc_count);
|
||
|
sub_worker.join();
|
||
|
});
|
||
|
sync.wait();
|
||
|
|
||
|
work(inc_count);
|
||
|
worker_thread.join();
|
||
|
|
||
|
EXPECT_EQ(shared_value, inc_count * 3);
|
||
|
}
|
||
|
|
||
|
TEST(OneVPL_SharedLock, ReadWrite_MultiThread)
|
||
|
{
|
||
|
SharedLock lock;
|
||
|
|
||
|
std::promise<void> barrier;
|
||
|
std::future<void> sync = barrier.get_future();
|
||
|
|
||
|
static const size_t inc_count = 10000000;
|
||
|
size_t shared_value = 0;
|
||
|
auto write_work = [&lock, &shared_value](size_t count) {
|
||
|
for (size_t i = 0; i < count; i ++) {
|
||
|
lock.lock();
|
||
|
shared_value ++;
|
||
|
lock.unlock();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
auto read_work = [&lock, &shared_value](size_t count) {
|
||
|
|
||
|
auto old_shared_value = shared_value;
|
||
|
for (size_t i = 0; i < count; i ++) {
|
||
|
lock.shared_lock();
|
||
|
EXPECT_TRUE(shared_value >= old_shared_value);
|
||
|
old_shared_value = shared_value;
|
||
|
lock.unlock_shared();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
std::thread writer_thread([&barrier, write_work] () {
|
||
|
barrier.set_value();
|
||
|
write_work(inc_count);
|
||
|
});
|
||
|
sync.wait();
|
||
|
|
||
|
read_work(inc_count);
|
||
|
writer_thread.join();
|
||
|
|
||
|
EXPECT_EQ(shared_value, inc_count);
|
||
|
}
|
||
|
|
||
|
|
||
|
TEST(OneVPL_ElasticBarrier, single_thread_visit)
|
||
|
{
|
||
|
TestBarrier barrier;
|
||
|
|
||
|
const size_t max_visit_count = 10000;
|
||
|
size_t visit_id = 0;
|
||
|
for (visit_id = 0; visit_id < max_visit_count; visit_id++) {
|
||
|
barrier.visit_in(visit_id);
|
||
|
EXPECT_EQ(barrier.visitors_in.size(), size_t{1});
|
||
|
}
|
||
|
EXPECT_EQ(barrier.last_visitor_id, size_t{0});
|
||
|
EXPECT_EQ(barrier.visitors_out.size(), size_t{0});
|
||
|
|
||
|
for (visit_id = 0; visit_id < max_visit_count; visit_id++) {
|
||
|
barrier.visit_out(visit_id);
|
||
|
EXPECT_EQ(barrier.visitors_in.size(), size_t{1});
|
||
|
}
|
||
|
EXPECT_EQ(barrier.last_visitor_id, visit_id - 1);
|
||
|
EXPECT_EQ(barrier.visitors_out.size(), size_t{1});
|
||
|
}
|
||
|
|
||
|
|
||
|
TEST(OneVPL_ElasticBarrier, multi_thread_visit)
|
||
|
{
|
||
|
TestBarrier tested_barrier;
|
||
|
|
||
|
static const size_t max_visit_count = 10000000;
|
||
|
std::atomic<size_t> visit_in_wait_counter{};
|
||
|
std::promise<void> start_sync_barrier;
|
||
|
std::shared_future<void> start_sync = start_sync_barrier.get_future();
|
||
|
std::promise<void> phase_sync_barrier;
|
||
|
std::shared_future<void> phase_sync = phase_sync_barrier.get_future();
|
||
|
|
||
|
auto visit_worker_job = [&tested_barrier,
|
||
|
&visit_in_wait_counter,
|
||
|
start_sync,
|
||
|
phase_sync] (size_t worker_id) {
|
||
|
|
||
|
start_sync.wait();
|
||
|
|
||
|
// first phase
|
||
|
const size_t begin_range = worker_id * max_visit_count;
|
||
|
const size_t end_range = (worker_id + 1) * max_visit_count;
|
||
|
for (size_t visit_id = begin_range; visit_id < end_range; visit_id++) {
|
||
|
tested_barrier.visit_in(visit_id);
|
||
|
}
|
||
|
|
||
|
// notify all worker first phase ready
|
||
|
visit_in_wait_counter.fetch_add(1);
|
||
|
|
||
|
// wait main second phase
|
||
|
phase_sync.wait();
|
||
|
|
||
|
// second phase
|
||
|
for (size_t visit_id = begin_range; visit_id < end_range; visit_id++) {
|
||
|
tested_barrier.visit_out(visit_id);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
auto visit_main_job = [&tested_barrier,
|
||
|
&visit_in_wait_counter,
|
||
|
&phase_sync_barrier] (size_t total_workers_count,
|
||
|
size_t worker_id) {
|
||
|
|
||
|
const size_t begin_range = worker_id * max_visit_count;
|
||
|
const size_t end_range = (worker_id + 1) * max_visit_count;
|
||
|
for (size_t visit_id = begin_range; visit_id < end_range; visit_id++) {
|
||
|
tested_barrier.visit_in(visit_id);
|
||
|
}
|
||
|
|
||
|
// wait all workers first phase done
|
||
|
visit_in_wait_counter.fetch_add(1);
|
||
|
while (visit_in_wait_counter.load() != total_workers_count) {
|
||
|
std::this_thread::yield();
|
||
|
};
|
||
|
|
||
|
// TEST invariant: last_visitor_id MUST be one from any FIRST worker visitor_id
|
||
|
bool one_of_available_ids_matched = false;
|
||
|
for (size_t id = 0; id < total_workers_count; id ++) {
|
||
|
size_t expected_last_visitor_for_id = id * max_visit_count;
|
||
|
one_of_available_ids_matched |=
|
||
|
(tested_barrier.last_visitor_id == expected_last_visitor_for_id) ;
|
||
|
}
|
||
|
EXPECT_TRUE(one_of_available_ids_matched);
|
||
|
|
||
|
// unblock all workers to work out second phase
|
||
|
phase_sync_barrier.set_value();
|
||
|
|
||
|
// continue second phase
|
||
|
for (size_t visit_id = begin_range; visit_id < end_range; visit_id++) {
|
||
|
tested_barrier.visit_out(visit_id);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
size_t max_worker_count = std::thread::hardware_concurrency();
|
||
|
if (max_worker_count < 2) {
|
||
|
max_worker_count = 2; // logical 2 threads required at least
|
||
|
}
|
||
|
std::vector<std::thread> workers;
|
||
|
workers.reserve(max_worker_count);
|
||
|
for (size_t worker_id = 1; worker_id < max_worker_count; worker_id++) {
|
||
|
workers.emplace_back(visit_worker_job, worker_id);
|
||
|
}
|
||
|
|
||
|
// let's go for first phase
|
||
|
start_sync_barrier.set_value();
|
||
|
|
||
|
// utilize main thread as well
|
||
|
visit_main_job(max_worker_count, 0);
|
||
|
|
||
|
// join all threads second phase
|
||
|
for (auto& w : workers) {
|
||
|
w.join();
|
||
|
}
|
||
|
|
||
|
// TEST invariant: last_visitor_id MUST be one from any LATTER worker visitor_id
|
||
|
bool one_of_available_ids_matched = false;
|
||
|
for (size_t id = 0; id < max_worker_count; id ++) {
|
||
|
one_of_available_ids_matched |=
|
||
|
(tested_barrier.last_visitor_id == ((id + 1) * max_visit_count - 1)) ;
|
||
|
}
|
||
|
EXPECT_TRUE(one_of_available_ids_matched);
|
||
|
}
|
||
|
}
|
||
|
} // opencv_test
|