Merge pull request #20705 from TolyaTalamanov:at/handle-reshape-in-gexecutor

G-API: Handle reshape for generic case in GExecutor

* Handle reshape for generic case for GExecutor

* Add initResources

* Add tests

* Refactor reshape method
This commit is contained in:
Anatoliy Talamanov 2021-09-23 22:59:40 +03:00 committed by GitHub
parent 54386c82fd
commit 499d8adb75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 230 additions and 4 deletions

View File

@ -7,8 +7,6 @@
#include "precomp.hpp"
#include <iostream>
#include <ade/util/zip_range.hpp>
#include <opencv2/gapi/opencv_includes.hpp>
@ -411,7 +409,8 @@ bool cv::gimpl::GExecutor::canReshape() const
{
// FIXME: Introduce proper reshaping support on GExecutor level
// for all cases!
return (m_ops.size() == 1) && m_ops[0].isl_exec->canReshape();
return std::all_of(m_ops.begin(), m_ops.end(),
[](const OpDesc& op) { return op.isl_exec->canReshape(); });
}
void cv::gimpl::GExecutor::reshape(const GMetaArgs& inMetas, const GCompileArgs& args)
@ -421,7 +420,17 @@ void cv::gimpl::GExecutor::reshape(const GMetaArgs& inMetas, const GCompileArgs&
ade::passes::PassContext ctx{g};
passes::initMeta(ctx, inMetas);
passes::inferMeta(ctx, true);
m_ops[0].isl_exec->reshape(g, args);
// NB: Before reshape islands need to re-init resources for every slot.
for (auto slot : m_slots)
{
initResource(slot.slot_nh, slot.data_nh);
}
for (auto& op : m_ops)
{
op.isl_exec->reshape(g, args);
}
}
void cv::gimpl::GExecutor::prepareForNewStream()

View File

@ -6,10 +6,158 @@
#include "../test_precomp.hpp"
#include "../gapi_mock_kernels.hpp"
namespace opencv_test
{
namespace
{
class GMockExecutable final: public cv::gimpl::GIslandExecutable
{
virtual inline bool canReshape() const override {
return m_priv->m_can_reshape;
}
virtual void reshape(ade::Graph&, const GCompileArgs&) override
{
m_priv->m_reshape_counter++;
}
virtual void handleNewStream() override { }
virtual void run(std::vector<InObj>&&, std::vector<OutObj>&&) { }
virtual bool allocatesOutputs() const override
{
return true;
}
virtual cv::RMat allocate(const cv::GMatDesc&) const override
{
m_priv->m_allocate_counter++;
return cv::RMat();
}
// NB: GMockBackendImpl creates new unique_ptr<GMockExecutable>
// on every compile call. Need to share counters between instances in order
// to validate it in tests.
struct Priv
{
bool m_can_reshape;
int m_reshape_counter;
int m_allocate_counter;
};
std::shared_ptr<Priv> m_priv;
public:
GMockExecutable(bool can_reshape = true)
: m_priv(new Priv{can_reshape, 0, 0})
{
};
void setReshape(bool can_reshape) { m_priv->m_can_reshape = can_reshape; }
int getReshapeCounter() const { return m_priv->m_reshape_counter; }
int getAllocateCounter() const { return m_priv->m_allocate_counter; }
};
class GMockBackendImpl final: public cv::gapi::GBackend::Priv
{
virtual void unpackKernel(ade::Graph &,
const ade::NodeHandle &,
const cv::GKernelImpl &) override { }
virtual EPtr compile(const ade::Graph &,
const cv::GCompileArgs &,
const std::vector<ade::NodeHandle> &) const override
{
++m_compile_counter;
return EPtr{new GMockExecutable(m_exec)};
}
mutable int m_compile_counter = 0;
GMockExecutable m_exec;
virtual bool controlsMerge() const override {
return true;
}
virtual bool allowsMerge(const cv::gimpl::GIslandModel::Graph &,
const ade::NodeHandle &,
const ade::NodeHandle &,
const ade::NodeHandle &) const override {
return false;
}
public:
GMockBackendImpl(const GMockExecutable& exec) : m_exec(exec) { };
int getCompileCounter() const { return m_compile_counter; }
};
class GMockFunctor : public gapi::cpu::GOCVFunctor
{
public:
GMockFunctor(cv::gapi::GBackend backend,
const char* id,
const Meta &meta,
const Impl& impl)
: gapi::cpu::GOCVFunctor(id, meta, impl), m_backend(backend)
{
}
cv::gapi::GBackend backend() const override { return m_backend; }
private:
cv::gapi::GBackend m_backend;
};
template<typename K, typename Callable>
GMockFunctor mock_kernel(const cv::gapi::GBackend& backend, Callable c)
{
using P = cv::detail::OCVCallHelper<Callable, typename K::InArgs, typename K::OutArgs>;
return GMockFunctor{ backend
, K::id()
, &K::getOutMeta
, std::bind(&P::callFunctor, std::placeholders::_1, c)
};
}
void dummyFooImpl(const cv::Mat&, cv::Mat&) { };
void dummyBarImpl(const cv::Mat&, const cv::Mat&, cv::Mat&) { };
struct GExecutorReshapeTest: public ::testing::Test
{
GExecutorReshapeTest()
: comp([](){
cv::GMat in;
cv::GMat out = I::Bar::on(I::Foo::on(in), in);
return cv::GComputation(in, out);
})
{
backend_impl1 = std::make_shared<GMockBackendImpl>(island1);
backend1 = cv::gapi::GBackend{backend_impl1};
backend_impl2 = std::make_shared<GMockBackendImpl>(island2);
backend2 = cv::gapi::GBackend{backend_impl2};
auto kernel1 = mock_kernel<I::Foo>(backend1, dummyFooImpl);
auto kernel2 = mock_kernel<I::Bar>(backend2, dummyBarImpl);
pkg = cv::gapi::kernels(kernel1, kernel2);
in_mat1 = cv::Mat::eye(32, 32, CV_8UC1);
in_mat2 = cv::Mat::eye(64, 64, CV_8UC1);
}
cv::GComputation comp;
GMockExecutable island1;
std::shared_ptr<GMockBackendImpl> backend_impl1;
cv::gapi::GBackend backend1;
GMockExecutable island2;
std::shared_ptr<GMockBackendImpl> backend_impl2;
cv::gapi::GBackend backend2;
cv::gapi::GKernelPackage pkg;
cv::Mat in_mat1, in_mat2, out_mat;;
};
} // anonymous namespace
// FIXME: avoid code duplication
// The below graph and config is taken from ComplexIslands test suite
TEST(GExecutor, SmokeTest)
@ -77,6 +225,75 @@ TEST(GExecutor, SmokeTest)
// with breakdown worked)
}
TEST_F(GExecutorReshapeTest, ReshapeInsteadOfRecompile)
{
// NB: Initial state
EXPECT_EQ(0, backend_impl1->getCompileCounter());
EXPECT_EQ(0, backend_impl2->getCompileCounter());
EXPECT_EQ(0, island1.getReshapeCounter());
EXPECT_EQ(0, island2.getReshapeCounter());
// NB: First compilation.
comp.apply(cv::gin(in_mat1), cv::gout(out_mat), cv::compile_args(pkg));
EXPECT_EQ(1, backend_impl1->getCompileCounter());
EXPECT_EQ(1, backend_impl2->getCompileCounter());
EXPECT_EQ(0, island1.getReshapeCounter());
EXPECT_EQ(0, island2.getReshapeCounter());
// NB: GMockBackendImpl implements "reshape" method,
// so it won't be recompiled if the meta is changed.
comp.apply(cv::gin(in_mat2), cv::gout(out_mat), cv::compile_args(pkg));
EXPECT_EQ(1, backend_impl1->getCompileCounter());
EXPECT_EQ(1, backend_impl2->getCompileCounter());
EXPECT_EQ(1, island1.getReshapeCounter());
EXPECT_EQ(1, island2.getReshapeCounter());
}
TEST_F(GExecutorReshapeTest, OneBackendNotReshapable)
{
// NB: Make first island not reshapable
island1.setReshape(false);
// NB: Initial state
EXPECT_EQ(0, backend_impl1->getCompileCounter());
EXPECT_EQ(0, island1.getReshapeCounter());
EXPECT_EQ(0, backend_impl2->getCompileCounter());
EXPECT_EQ(0, island2.getReshapeCounter());
// NB: First compilation.
comp.apply(cv::gin(in_mat1), cv::gout(out_mat), cv::compile_args(pkg));
EXPECT_EQ(1, backend_impl1->getCompileCounter());
EXPECT_EQ(1, backend_impl2->getCompileCounter());
EXPECT_EQ(0, island1.getReshapeCounter());
EXPECT_EQ(0, island2.getReshapeCounter());
// NB: Since one of islands isn't reshapable
// the entire graph isn't reshapable as well.
comp.apply(cv::gin(in_mat2), cv::gout(out_mat), cv::compile_args(pkg));
EXPECT_EQ(2, backend_impl1->getCompileCounter());
EXPECT_EQ(2, backend_impl2->getCompileCounter());
EXPECT_EQ(0, island1.getReshapeCounter());
EXPECT_EQ(0, island2.getReshapeCounter());
}
TEST_F(GExecutorReshapeTest, ReshapeCallAllocate)
{
// NB: Initial state
EXPECT_EQ(0, island1.getAllocateCounter());
EXPECT_EQ(0, island1.getReshapeCounter());
// NB: First compilation.
comp.apply(cv::gin(in_mat1), cv::gout(out_mat), cv::compile_args(pkg));
EXPECT_EQ(1, island1.getAllocateCounter());
EXPECT_EQ(0, island1.getReshapeCounter());
// NB: The entire graph is reshapable, so it won't be recompiled, but reshaped.
// Check that reshape call "allocate" to reallocate buffers.
comp.apply(cv::gin(in_mat2), cv::gout(out_mat), cv::compile_args(pkg));
EXPECT_EQ(2, island1.getAllocateCounter());
EXPECT_EQ(1, island1.getReshapeCounter());
}
// FIXME: Add explicit tests on GMat/GScalar/GArray<T> being connectors
// between executed islands