Skip to content
Snippets Groups Projects
Commit aba656b1 authored by Hadrien G's avatar Hadrien G Committed by Hadrien Benjamin Grasland
Browse files

Please clang-format and clean up docs along the way

parent f6e6990c
No related branches found
No related tags found
No related merge requests found
...@@ -18,184 +18,176 @@ ...@@ -18,184 +18,176 @@
#ifndef ACTFW_CONCURRENCY_PARALLEL_FOR_H #ifndef ACTFW_CONCURRENCY_PARALLEL_FOR_H
#define ACTFW_CONCURRENCY_PARALLEL_FOR_H 1 #define ACTFW_CONCURRENCY_PARALLEL_FOR_H 1
#include <exception>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <boost/variant/get.hpp> #include <boost/variant/get.hpp>
#include <boost/variant/variant.hpp> #include <boost/variant/variant.hpp>
#include <exception>
#include "ACTFW/Framework/ProcessCode.hpp" #include "ACTFW/Framework/ProcessCode.hpp"
// Let's keep as much as possible inside of namespaces // Let's keep as much as possible inside of namespaces
namespace FW { namespace FW {
namespace Details { namespace Details {
// A C++ for loop iteration may either run normally, break, continue, return,
// or throw. Scenarios which do not emit data can be expressed via an enum...
enum class LoopFlow { NormalIteration, Continue, Break };
// ...from which one can derive a Boost variant which covers the complete set
// of look iteration outcomes, including early returns and exceptions.
template <typename ReturnT>
using LoopOutcome = boost::variant<LoopFlow, ReturnT, std::exception_ptr>;
// Tell whether a certain loop iteration outcome requires exiting the loop
template <typename T>
bool
loop_outcome_is_fatal(const LoopOutcome<T>& outcome)
{
// Any outcome which is not representable by LoopFlow is fatal
if (outcome.which() != 0) return true;
// Among the outcomes representable by LoopFlow, only Break is fatal
return (outcome == LoopOutcome<T>(LoopFlow::Break));
}
// A C++ for loop iteration may either run normally, break, continue, return, or // This macro wraps a user-provided for loop iteration code into a functor
// throw an exception. Scenarios which do not emit data are handled via an enum. // which returns a LoopOutcome. It handles all loop iteration outcomes which
enum class LoopFlow { NormalIteration, Continue, Break }; // can only be handled by a macro, that is, everything but exceptions.
//
// The RETURN_TYPE parameter indicates the return type of the function inside
// The complete set of possible loop outcomes is handled via a Boost variant, // which the underlying for loop is located, and INDEX is the name of the loop
// including all LoopFlow and adding support for early returns and exceptions. // variable expected by the user's iteration code.
template <typename T> //
using LoopOutcome = boost::variant<LoopFlow, T, std::exception_ptr>; // Ideally, RETURN_TYPE should be inferred instead of caller-provider, but
// that does not seem possible in C++14. I think C++17's flavor of auto is
// powerful enough for such inference, so maybe something for the future...
// Tell whether a certain loop iteration outcome requires exiting the loop //
template <typename T> #define _ACTFW_WRAPPED_LOOP_ITERATION(RETURN_TYPE, INDEX, ...) \
bool /* To produce a functor in a random context, we use a catch-all lambda */ \
loop_outcome_is_fatal(const LoopOutcome<T>& outcome) { [&](size_t INDEX) -> FW::Details::LoopOutcome<RETURN_TYPE> { \
// Any outcome which is not representable by LoopFlow is fatal /* We set up a fake for loop environment to catch all user actions */ \
if(outcome.which() != 0) return true; for (int _dummy_##INDEX = 0; _dummy_##INDEX < 2; ++_dummy_##INDEX) { \
if (_dummy_##INDEX == 0) { \
// Among the outcomes representable by LoopFlow, only Break is fatal /* The user's loop iteration code is pasted here. It may return */ \
return (outcome == LoopOutcome<T>(LoopFlow::Break)); /* or throw an exception, that is handled in higher-level code */ \
} __VA_ARGS__ \
// This macro wraps a user-provided for loop iteration into a functor which
// returns a LoopOutcome. It handles all loop iteration outcomes which require
// a macro to be handled, namely everything but exceptions.
//
// The RETURN_TYPE parameter indicates the return type of the function inside
// which the for loop is located, and the INDEX type is the name of the expected
// loop variable.
//
// I would like the return type of the functor to be inferred, but that does not
// seem possible in C++14. In C++17, replacing RETURN_TYPE with "auto" in the
// lambda's return type should be legal and do the expected thing.
//
#define _ACTFW_WRAPPED_LOOP_ITERATION(RETURN_TYPE, INDEX, ...) \
[&](size_t INDEX) -> FW::Details::LoopOutcome<RETURN_TYPE> { \
/* We set up a fake for loop environment to catch all user actions */ \
for(int _dummy_##INDEX = 0; _dummy_##INDEX < 2; ++_dummy_##INDEX) { \
if(_dummy_##INDEX == 0) { \
/* The user's loop iteration code is pasted here. It may return */ \
/* or throw an exception, that is handled in higher-level C++ code */ \
__VA_ARGS__ \
\ \
/* If control reaches this point, the loop iteration code finished */ \ /* If control reaches this point, the loop iteration code */ \
/* normally without continuing, breaking, or returning */ \ /* finished normally without continuing, breaking, or returning */ \
return FW::Details::LoopFlow::NormalIteration; \ return FW::Details::LoopFlow::NormalIteration; \
} else { \ } else { \
/* If control reaches this point, the loop iteration was skipped */ \ /* If control reaches this point, the loop iteration was skipped */ \
/* using the "continue" control flow keyword */ \ /* using the "continue" control flow keyword */ \
return FW::Details::LoopFlow::Continue; \ return FW::Details::LoopFlow::Continue; \
} \
} \ } \
} \
\ \
/* It control reaches this point, the loop was aborted using the */ \ /* It control reaches this point, the loop was aborted using the */ \
/* "break" control flow keyword */ \ /* "break" control flow keyword */ \
return FW::Details::LoopFlow::Break; \ return FW::Details::LoopFlow::Break; \
} }
// Thanks to the WRAPPED_LOOP_ITERATION macro, most of the ACTSFW parallel loop // Thanks to the loop iteration wrapper above, most of the ACTSFW parallel
// wrapper can now be written as real C++ code, rather than macro black magic. // for loop can now be written as real C++ code, instead of a macro.
//
// This function runs a parallel for loop, with loop iterations going from
// "start" to "end", and a per-iteration behavior specified by a functor taking
// the current parallel loop index as a parameter, and returning a loop
// iteration outcome. The functor is expected to be generated via the
// _ACTFW_WRAPPED_LOOP_ITERATION macro.
//
// If there was an early return from the loop, this function propagates it.
// Otherwise, it returns an empty boost::optional.
//
template <typename T>
boost::optional<T>
parallel_for_impl(size_t start,
size_t end,
std::function<LoopOutcome<T>(size_t)> iteration)
{
// These control variables are used to tell OpenMP to exit the parallel loop
// early and to record why we had to do it.
// //
// TODO: Once we can assume good OpenMP 4.0 support from the host compiler, // This function runs a parallel for loop, with loop iterations going from
// break out of the loop more efficiently using #pragma omp cancel // "start" to "end", and a per-iteration behavior specified by a functor
// taking the current parallel loop index as a parameter, and returning a loop
// iteration outcome. That functor is expected to be generated via the
// previously defined _ACTFW_WRAPPED_LOOP_ITERATION macro.
// //
bool exit_loop_early = false; // If there was an early return from the loop, this function propagates it up.
LoopOutcome<T> exit_reason = LoopFlow::NormalIteration; // Otherwise, it returns an empty boost::optional.
//
// Our parallel for loop is implemented using OpenMP template <typename T>
#pragma omp parallel for boost::optional<T>
for(size_t index = start; index < end; ++index) { parallel_for_impl(size_t start,
// Skip remaining loop iterations if asked to exit the loop early size_t end,
#pragma omp flush(exit_loop_early) std::function<LoopOutcome<T>(size_t)> iteration)
if(exit_loop_early) continue; {
// These control variables are used to tell OpenMP to exit the parallel loop
// Run this loop iteration and record the outcome, exceptions included // early if needed, and to record why we had to do it.
LoopOutcome<T> outcome = LoopFlow::NormalIteration; //
try { // TODO: Once we can assume good OpenMP 4.0 support from the host compiler,
outcome = iteration(index); // break out of the loop more efficiently using #pragma omp cancel
} catch(...) { //
outcome = std::current_exception(); bool exit_loop_early = false;
} LoopOutcome<T> exit_reason = LoopFlow::NormalIteration;
// Our parallel for loop is implemented using OpenMP
#pragma omp parallel for
for (size_t index = start; index < end; ++index) {
// Skip remaining loop iterations if asked to exit the loop early
#pragma omp flush(exit_loop_early)
if (exit_loop_early) continue;
// Run this loop iteration and record the outcome, exceptions included
LoopOutcome<T> outcome = LoopFlow::NormalIteration;
try {
outcome = iteration(index);
} catch (...) {
outcome = std::current_exception();
}
// Abort the loop if the iteration's outcome states that we should do so // Abort the loop if the iteration's outcome states that we should do so
if(loop_outcome_is_fatal(outcome)) { if (loop_outcome_is_fatal(outcome)) {
#pragma omp critical #pragma omp critical
{ {
exit_reason = std::move(outcome); exit_reason = std::move(outcome);
exit_loop_early = true; exit_loop_early = true;
#pragma omp flush(exit_loop_early) #pragma omp flush(exit_loop_early)
}
} }
} }
}
// Analyze the loop termination cause and react accordingly // Analyze the loop termination cause and react accordingly
switch(exit_reason.which()) { switch (exit_reason.which()) {
// The loop exited normally or via break, no need to do anything // The loop exited normally or via break, no need to do anything
case 0: case 0:
return boost::optional<T>(); return boost::optional<T>();
// The loop was exited due to an early return, propagate it up the stack // The loop was exited due to an early return, propagate it up the stack
case 1: case 1:
return boost::get<T>(std::move(exit_reason)); return boost::get<T>(std::move(exit_reason));
// The loop was exited because an exception was thrown. Rethrow it. // The loop was exited because an exception was thrown. Rethrow it.
case 2: case 2:
auto exception = boost::get<std::exception_ptr>(std::move(exit_reason)); auto exception = boost::get<std::exception_ptr>(std::move(exit_reason));
std::rethrow_exception(std::move(exception)); std::rethrow_exception(std::move(exception));
}
} }
}
/// The following macro is the out-of-order multithreaded equivalent of the
/// Out-of-order multithreaded equivalent of the following serial loop /// following serial loop (macro arguments capitalized for emphasis):
/// (macro arguments capitalized for emphasis): ///
/// /// for(size_t INDEX = START; index < END; ++index) {
/// for(size_t INDEX = START; index < END; ++index) { /// ...
/// ... /// }
/// } ///
/// /// Unlike raw OpenMP 3.1, we also support breaks, early returns, and
/// Unlike raw OpenMP 3.1, we also support breaks, early returns, and exception /// exception propagation, to allow for more idiomatic C++ code. On the other
/// propagation, in order to allow for more idiomatic C++ code. On the other /// hand, due to the way the C preprocessor handles macro arguments, the loop
/// hand, due to the way the C preprocessor handles macro arguments, the loop /// iteration code should not contain any preprocessor directive.
/// iteration code should contain no preprocessor directive. ///
/// /// Due to limitations of C++14's type inference, this macro may currently
/// Due to limitations of C++14's type inference, this macro may only be called /// only be called in a function which returns FW::ProcessCode. This
/// in a function which returns FW::ProcessCode. /// restriction may be lifted in the future if C++ type inference improves.
/// ///
/// @param index must be unique within the enclosing scope. #define ACTFW_PARALLEL_FOR(INDEX, START, END, ...) \
/// /* This dummy do-while asserts that the macro's output is a statement */ \
#define ACTFW_PARALLEL_FOR(INDEX, START, END, ...) \ do { \
/* This dummy do-while makes sure that the macro's output is a statement */ \ /* Execute the parallel for loop */ \
do { \ auto optional_early_return \
/* Execute the parallel for loop */ \ = FW::Details::parallel_for_impl<FW::ProcessCode>( \
auto optional_early_return = \ (START), \
FW::Details::parallel_for_impl<FW::ProcessCode>( \ (END), \
(START), \ _ACTFW_WRAPPED_LOOP_ITERATION( \
(END), \ FW::ProcessCode, INDEX, __VA_ARGS__)); \
_ACTFW_WRAPPED_LOOP_ITERATION(FW::ProcessCode, \
INDEX, \
__VA_ARGS__) \
); \
\ \
/* Return early from the host function if asked to do so */ \ /* Return early from the host function if asked to do so */ \
if(optional_early_return) { \ if (optional_early_return) { \
return *optional_early_return; \ return *optional_early_return; \
} \ } \
} while(false); } while (false);
} }
} }
......
...@@ -147,41 +147,33 @@ FW::Sequencer::run(boost::optional<size_t> events, size_t skip) ...@@ -147,41 +147,33 @@ FW::Sequencer::run(boost::optional<size_t> events, size_t skip)
// Execute the event loop // Execute the event loop
ACTS_INFO("Run the event loop"); ACTS_INFO("Run the event loop");
ACTFW_PARALLEL_FOR( ACTFW_PARALLEL_FOR(ievent, 0, numEvents, {
ievent, const size_t event = skip + ievent;
0, ACTS_INFO("start event " << event);
numEvents,
{ // Setup the event and algorithm context
const size_t event = skip + ievent; WhiteBoard eventStore(Acts::getDefaultLogger(
ACTS_INFO("start event " << event); "EventStore#" + std::to_string(event), m_cfg.eventStoreLogLevel));
size_t ialg = 0;
// Setup the event and algorithm context
WhiteBoard eventStore(Acts::getDefaultLogger( // read everything in
"EventStore#" + std::to_string(event), m_cfg.eventStoreLogLevel)); for (auto& rdr : m_readers) {
size_t ialg = 0; if (rdr->read({ialg++, event, eventStore}) != ProcessCode::SUCCESS)
return ProcessCode::ABORT;
// read everything in
for (auto& rdr
: m_readers) {
if (rdr->read({ialg++, event, eventStore}) != ProcessCode::SUCCESS)
return ProcessCode::ABORT;
}
// process all algorithms
for (auto& alg
: m_algorithms) {
if (alg->execute({ialg++, event, eventStore}) != ProcessCode::SUCCESS)
return ProcessCode::ABORT;
}
// write out results
for (auto& wrt
: m_writers) {
if (wrt->write({ialg++, event, eventStore}) != ProcessCode::SUCCESS)
return ProcessCode::ABORT;
}
ACTS_INFO("event " << event << " done");
} }
) // process all algorithms
for (auto& alg : m_algorithms) {
if (alg->execute({ialg++, event, eventStore}) != ProcessCode::SUCCESS)
return ProcessCode::ABORT;
}
// write out results
for (auto& wrt : m_writers) {
if (wrt->write({ialg++, event, eventStore}) != ProcessCode::SUCCESS)
return ProcessCode::ABORT;
}
ACTS_INFO("event " << event << " done");
})
// Call endRun() for writers and services // Call endRun() for writers and services
ACTS_INFO("Running end-of-run hooks of writers and services"); ACTS_INFO("Running end-of-run hooks of writers and services");
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment