Closed PeterBowman closed 4 years ago
Parallelization could be easily achieved with <future>
:
I'd fancy an abstract DeviceMapper
class and SequentialDeviceMapper
+ ConcurrentDeviceMapper
subclasses, such that the latter manages a pool of threads given an arbitrary number of callbacks (one per joint).
C++17 introduces execution policies which allow to easily switch between sequential and parallel loops via std::for_each
algorithm: SO answer.
In C++11, I have to resort to playing with std::launch
policies in std::async
.
The DeviceMapper
class was added in https://github.com/roboticslab-uc3m/yarp-devices/commit/de805265a89fb86e4b5defe7727cbb5849645336. I plan on moving it to its own library (static/shared), write unit tests and enable parallelization (currently WIP at https://github.com/roboticslab-uc3m/yarp-devices/commit/7a7bf3fcc559e18a26062042c881dfa38798eec9).
Not sure about enabling thread limits, i.e. fix the max number of parallel tasks this class may start so that additional tasks are enqueued for later.
SO Q/A regarding static/shared library performance:
Regarding thread limits: SO, reddit.
Two nice blog entries about C++11 move semantics: ref1, ref2.
Quite interesting links from https://github.com/roboticslab-uc3m/yarp-devices/commit/8dcd9ec2498d4c58457cfff91205755d110aff8c regarding template specialization/instantiation:
Ready at https://github.com/roboticslab-uc3m/yarp-devices/commit/3c6efbbfd96a8fcc6f603fdf1a3a7c3a86ffe5ac.
Not sure about enabling thread limits, i.e. fix the max number of parallel tasks this class may start so that additional tasks are enqueued for later.
Using sequential policy by default (single-threaded), an arbitrary large number of threads can be enabled after instantiation via YARP property.
Ready at 3c6efbb.
This is a naive solution, it splits requested callbacks into two groups: those executed asynchronously in threads and the remaining sequential ones. Ideally, freed threads should allocate new tasks. This is achieved with a nice thread pool library I borrowed from https://github.com/vit-vit/CTPL: https://github.com/roboticslab-uc3m/yarp-devices/commit/403ade07dce26b4fe34ed77dee626dd575a6061a. Results are even superior in equality of conditions (same number of tasks and threads): FutureTask.zip.
I realized this parallelization is actually meaningless since high frequency commands are further transmitted via TPDO protocol (https://github.com/roboticslab-uc3m/yarp-devices/issues/232). It only made sense for SDO multi-joint requests due to the confirmed nature of these transfers (every SDO client request/indication awaits a response/confirmation from the SDO server), hence sequentially applying this request-and-wait scheme for several joint commands would result in a linear increase of the overall wait.
Cons:
Therefore, I'm definitely disabling this feature for YARP motor commands. However, I found this actually helpful for synchronization of multiple CAN buses (https://github.com/roboticslab-uc3m/yarp-devices/issues/226): https://github.com/roboticslab-uc3m/yarp-devices/commit/474556176e8f8c831e8ac5f79389f01b9ea2d45b.
Note YARP's fakeMotionControl device showcases a similar technique with a sequential implementation, split into specific interfaces: yarp::dev::ImplementPositionControl
, yarp::dev::ImplementVelocityControl
... Therefore, most .cpp files in the CanBusControlboard tree could be removed in favor of this class of ours inheriting from the aforementioned YARP concrete classes, assuming proper joint index configuration on init.
Split from https://github.com/roboticslab-uc3m/yarp-devices/issues/209#issuecomment-518217324: