[WIP] eventify: removed support for non-prioritized task_types #16

Member
No description provided.
~ The misleadingly named Task::executor_ data member
   has been renamed to Task::processor_,

   because Tasks are parameterized by concrete Processors,
   not Executors,
   what can be observed in the TaskFactory class.

 ~ The Executor's destructor is no longer virtual,
   since there does not exist a class inheriting from Executor.
This change drops support for non-prioritized task_types from Eventify
and requires FMSolvr to specify all of its task_types at compile-time.

Essentially,

 - the flexibility of storing polymorphically non-prioritized tasks
   in a single queue of BaseTasks in each MultiQueue<> instance, and

 - the ability to specify non-prioritized task_types at run-time

have been removed from Eventify. Superficially, this might sound negative.

However, if an Eventify client needs the removed features, it can still:

 - implement a polymorphic value type,
   representing a task of any type, using run-time concept idiom,

 - and then, pass that class as the last task_type to the MulitQueue<>.

This effectively will imitate the existence of
a single queue storing polymorphically all non-prioritized tasks,

without any loss of efficiency.

The opposite is not true, however - always creating a queue of BaseTasks
forces all Eventify clients to pay for run-time polymorphism,

even in cases, when it is entirely unnecessary, such as in FMSolver.

More info about the run-time concept idiom can be found here:

 - NDC { London } Sean Parent - Better Code: Runtime Polymorphism

   ~ https://www.youtube.com/watch?v=QGcVXgEVMJg
   ~ https://sean-parent.stlab.cc/papers-and-presentations
Before this change,
Tasks were stored in concurrent_queues indirectly, through pointers:

                    +---------------------------------   -----+
  concurrent_queue  |   |   |   |   |   |   |   |   | ... |   |
                    +-|---|---|---|---|---|---|---|---   ---|-+
                      |   |   |   |   |   |   |   |         |
                      V   |   V   |   V   |   V   |         V
                    +---+ | +---+ | +---+ | +---+ |       +---+
                    |   | | |   | | |   | | |   | |       |   |
                    |   | | |   | | |   | | |   | |       |   |
                    |   | | |   | | |   | | |   | |       |   |
                    +---+ | +---+ | +---+ | +---+ |       +---+
       Tasks              V       V       V       V  
                        +---+   +---+   +---+   +---+
                        |   |   |   |   |   |   |   |
                        |   |   |   |   |   |   |   |
                        |   |   |   |   |   |   |   |
                        +---+   +---+   +---+   +---+

However, dynamically-allocating each Task is not necessary,
since Eventify requires specifying all task_types at compile-time.

This essentially means that a MultiQueue can be represented as
a std::tuple<> of concurrent_queues,
each storing directly (by value) Tasks of a single task_type:

                    +---------------------------------   -----+
  concurrent_queue  |   |   |   |   |   |   |   |   |     |   |
                    |   |   |   |   |   |   |   |   | ... |   |
       Tasks        |   |   |   |   |   |   |   |   |     |   |
                    +---------------------------------   -----+


Note that, before the change "eventify: removed abstract class BaseTask",
each MultiQueue contained single concurrent_queue dedicated to
storing polymorphically Tasks of all non-priorities task_types.

This explains why Tasks were
dynamically-allocated and stored indirectly (by pointer), that is,

in C++ polymorphic value_types require specialized implementation,
thus developers often use pointers, which are polymorphic out-of-the-box.


Unfortunately, this change is suboptimal,
because semantically each TaskFactory::CreateAndEnqueueTask() operation
creates a couple of redundant Task copies, which can be optimized away,
but that optimization is not guaranteed.

Ideally, invoking TaskFactory::CreateAndEnqueueTask() would result in
emplacing a Task directly in a concurrent_queue.

However, such implementation would require non-trivial refactoring
of the TaskFactory, LoadBalancer and MultiQueue classes,
therefore, for the time being,
Eventify will rely on the optimizer to eliminate redundant copies.
The concurrent_queue::try_pop_front() function
relies on an implicit conversion, because
it returns std::optional<task> and queue.front() returns a task.


This implicit conversion from a task to std::optional<task> is not ideal:

 - The implicit conversion is performed when std::mutex is still locked,
   which hinders scalability of Eventify.

 - C++ compilers cannot perform copy elision,
   when the return type of a function
   does not match the type of the returned object.

[C++ reference] - Copy elision
~ https://en.cppreference.com/w/cpp/language/copy_elision
m.zych changed title from [WIP] eventify: removed support for non-prioritized Tasks and stopped dynamically-allocating Tasks to [WIP] eventify: removed support for non-prioritized task_types 2022-03-11 16:32:39 +01:00
i.kabadshow merged commit 6459ec3d15 into WIP/parallelization/intra-node/lgpl21+minimize 2022-03-14 22:16:55 +01:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: ATML-CAP/fmsolvr#16
No description provided.