Skip to content

Commit

Permalink
Bypass task to operator()() to remove extra synchronization complexities
Browse files Browse the repository at this point in the history
Signed-off-by: pavelkumbrasev <[email protected]>
  • Loading branch information
pavelkumbrasev committed Sep 21, 2023
1 parent b4572f0 commit 4107ced
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 52 deletions.
22 changes: 9 additions & 13 deletions examples/migration/recursive_fibonacci/fibonacci_single_task.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ struct single_fib_task : task_emulation::base_task {
single_fib_task(int n, int* x) : n(n), x(x), s(state::compute)
{}

void execute() override {
task_emulation::base_task* execute() override {
task_emulation::base_task* bypass = nullptr;
switch (s) {
case state::compute : {
compute_impl();
bypass = compute_impl();
break;
}
case state::sum : {
Expand All @@ -51,35 +52,30 @@ struct single_fib_task : task_emulation::base_task {
if (tesing_enabled) {
if (n == cutoff && num_recycles > 0) {
--num_recycles;
compute_impl();
bypass = compute_impl();
}
}

break;
}
}
return bypass;
}

void compute_impl() {
task_emulation::base_task* compute_impl() {
task_emulation::base_task* bypass = nullptr;
if (n < cutoff) {
*x = serial_fib_1(n);
}
else {
auto bypass = this->allocate_child_and_increment<single_fib_task>(n - 2, &x_r);
bypass = this->allocate_child_and_increment<single_fib_task>(n - 2, &x_r);
task_emulation::run_task(this->allocate_child_and_increment<single_fib_task>(n - 1, &x_l));

// Recycling
this->s = state::sum;
this->recycle_as_continuation();

// Bypass is not supported by task_emulation and next_task executed directly.
// However, the old-TBB bypass behavior can be achieved with
// `return task_group::defer()` (check Migration Guide).
// Consider submit another task if recursion call is not acceptable
// i.e. instead of Direct Body call
// submit task_emulation::run_task(this->allocate_child_and_increment<single_fib_task>(n - 2, &x_r));
bypass->operator()();
}
return bypass;
}


Expand Down
16 changes: 6 additions & 10 deletions examples/migration/recursive_fibonacci/fibonacci_two_tasks.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ long serial_fib(int n) {
struct fib_continuation : task_emulation::base_task {
fib_continuation(int& s) : sum(s) {}

void execute() override {
task_emulation::base_task* execute() override {
sum = x + y;
return nullptr;
}

int x{ 0 }, y{ 0 };
Expand All @@ -44,7 +45,8 @@ struct fib_continuation : task_emulation::base_task {
struct fib_computation : task_emulation::base_task {
fib_computation(int n, int* x) : n(n), x(x) {}

void execute() override {
task_emulation::base_task* execute() override {
task_emulation::base_task* bypass = nullptr;
if (n < cutoff) {
*x = serial_fib(n);
}
Expand All @@ -57,15 +59,9 @@ struct fib_computation : task_emulation::base_task {
this->recycle_as_child_of(c);
n = n - 2;
x = &c.y;

// Bypass is not supported by task_emulation and next_task executed directly.
// However, the old-TBB bypass behavior can be achieved with
// `return task_group::defer()` (check Migration Guide).
// Consider submit another task if recursion call is not acceptable
// i.e. instead of Recycling + Direct Body call
// submit task_emulation::run_task(c.create_child<fib_computation>(n - 2, &c.y));
this->operator()();
bypass = this;
}
return bypass;
}

int n;
Expand Down
58 changes: 29 additions & 29 deletions examples/migration/recursive_fibonacci/task_emulation_layer.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,41 +47,51 @@ class base_task {
public:
base_task() = default;

base_task(const base_task& t) : m_type(t.m_type.load()), m_parent(t.m_parent), m_ref_counter(t.m_ref_counter.load())
base_task(const base_task& t) : m_type(t.m_type), m_parent(t.m_parent), m_ref_counter(t.m_ref_counter.load())
{}

virtual ~base_task() = default;

void operator() () const {
base_task* parent_snapshot = m_parent;
std::uint64_t type_snapshot = m_type;
task_type type_snapshot = m_type;

const_cast<base_task*>(this)->execute();
base_task* bypass = const_cast<base_task*>(this)->execute();

bool is_task_recycled_as_child = parent_snapshot != m_parent;
bool is_task_recycled_as_continuation = type_snapshot != m_type;

if (m_parent && !is_task_recycled_as_child && !is_task_recycled_as_continuation) {
auto child_ref = m_parent->remove_child_reference() & (m_self_ref - 1);
if (child_ref == 0) {
if (m_parent->remove_child_reference() == 0) {
m_parent->operator()();
}
}

if (type_snapshot != task_type::stack_based && const_cast<base_task*>(this)->remove_self_ref() == 0) {
if (type_snapshot != task_type::stack_based && !is_task_recycled_as_child && !is_task_recycled_as_continuation) {
delete this;
}

if (bypass != nullptr) {
m_type = type_snapshot;

// Bypass is not supported by task_emulation and next_task executed directly.
// However, the old-TBB bypass behavior can be achieved with
// `return task_group::defer()` (check Migration Guide).
// Consider submit another task if recursion call is not acceptable
// i.e. instead of Direct Body call
// submit task_emulation::run_task();
bypass->operator()();
}
}

virtual void execute() = 0;
virtual base_task* execute() = 0;

template <typename C, typename... Args>
C* allocate_continuation(std::uint64_t ref, Args&&... args) {
C* continuation = new C{std::forward<Args>(args)...};
continuation->m_type = task_type::continuation;
continuation->m_type = task_type::allocated;
continuation->reset_parent(reset_parent());
continuation->m_ref_counter = ref;
continuation->add_self_ref();
return continuation;
}

Expand Down Expand Up @@ -109,12 +119,12 @@ class base_task {

template <typename C>
void recycle_as_child_of(C& c) {
m_type = task_type::recycled;
reset_parent(&c);
}

void recycle_as_continuation() {
add_self_ref();
m_type += task_type::continuation;
m_type = task_type::recycled;
}

void add_child_reference() {
Expand All @@ -126,21 +136,13 @@ class base_task {
}

protected:
void add_self_ref() {
m_ref_counter.fetch_add(m_self_ref);
}

std::uint64_t remove_self_ref() {
return m_ref_counter.fetch_sub(m_self_ref) - m_self_ref;
}

struct task_type {
static constexpr std::uint64_t stack_based = 1;
static constexpr std::uint64_t allocated = 1 << 1;
static constexpr std::uint64_t continuation = 1 << 2;
enum task_type {
stack_based,
allocated,
recycled
};

std::atomic<std::uint64_t> m_type;
mutable task_type m_type;

private:
template <typename F, typename... Args>
Expand All @@ -161,7 +163,6 @@ class base_task {
F* allocate_child_impl(Args&&... args) {
F* obj = new F{std::forward<Args>(args)...};
obj->m_type = task_type::allocated;
obj->add_self_ref();
obj->reset_parent(this);
return obj;
}
Expand All @@ -173,20 +174,20 @@ class base_task {
}

base_task* m_parent{nullptr};
static constexpr std::uint64_t m_self_ref = std::uint64_t(1) << 48;
std::atomic<std::uint64_t> m_ref_counter{0};
};

class root_task : public base_task {
public:
root_task(tbb::task_group& tg) : m_tg(tg), m_callback(m_tg.defer([] { /* Create empty callback to preserve reference for wait. */})) {
add_child_reference();
m_type = base_task::task_type::continuation;
m_type = base_task::task_type::allocated;
}

private:
void execute() override {
base_task* execute() override {
m_tg.run(std::move(m_callback));
return nullptr;
}

tbb::task_group& m_tg;
Expand All @@ -205,7 +206,6 @@ template <typename F, typename... Args>
F* allocate_root_task(tbb::task_group& tg, Args&&... args) {
F* obj = new F{std::forward<Args>(args)...};
obj->m_type = base_task::task_type::allocated;
obj->add_self_ref();
obj->reset_parent(new root_task{tg});
return obj;
}
Expand Down

0 comments on commit 4107ced

Please sign in to comment.