diff --git a/src/scheduler/Scheduler.cpp b/src/scheduler/Scheduler.cpp index 6f9dea3ae0b5d27e3fdea36adc0478deeb815a05..08a7c274b40f181f147ed1b97c79a465153ab1d6 100644 --- a/src/scheduler/Scheduler.cpp +++ b/src/scheduler/Scheduler.cpp @@ -45,8 +45,8 @@ void Aidge::SequentialScheduler::generateScheduling(bool verbose) { // TODO: handle memory allocation in scheduler // TODO: optimize memory usage - // Setup initial potential consumers list: - // List of input nodes + // 1) Setup initial consumers list: + // It is the list of input nodes std::set<std::shared_ptr<Node>> consumers = mGraphView->inputNodes(); // Plus the list of nodes inside the graph connected to an inner producer std::set<std::shared_ptr<Node>> producers; @@ -58,14 +58,6 @@ void Aidge::SequentialScheduler::generateScheduling(bool verbose) { const auto producersConsumers = getConsumers(producers); consumers.insert(producersConsumers.begin(), producersConsumers.end()); - // Frozen consumers is used as a stop condition of the scheduling loop: - // The first time no consumer is runnable, frozenConsumers is updated to the - // current list of consumer. If after successive iterations, all with no - // runnable consumer, the list of consumer is again equal to frozenConsumers - // it means we are in cycle with no more scheduling update, a.k.a. a - // frozen state. - std::vector<std::set<std::shared_ptr<Node>>> frozenConsumers; - std::map<std::shared_ptr<Node>, std::string> namePtrTable; if (verbose) namePtrTable = mGraphView->getRankedNodesName("{0} ({1}#{3})"); @@ -77,14 +69,16 @@ void Aidge::SequentialScheduler::generateScheduling(bool verbose) { mStaticSchedule.push_back(std::vector<std::shared_ptr<Node>>()); do { - // From the current consumers list, check if any prior nodes are needed. + // 2) From the current consumers list, check if any prior consumer node + // is needed. A prior will generally be required for any node consuming + // parameters (weights and bias) that is not an input node. // If for a given node, only parent producers (at any depth) are needed // to satisfy its required data, it becomes a prior. // If the prior node is a producer, it is added to the list of required // producers. // If the prior node is of another type, it replaces the initial consumer - // in the new priorConsumers list. The initial consumer will necessarily - // be added again later in the consumers list. + // in the new priorConsumers list. The initial consumer will become + // again a consumer later, by construction. if (verbose) printf("List of consumers with their priors:\n"); std::set<std::shared_ptr<Node>> requiredProducers; std::set<std::shared_ptr<Node>> priorConsumers; @@ -124,15 +118,23 @@ void Aidge::SequentialScheduler::generateScheduling(bool verbose) { } } + // 3) Prior consumers replace the initial consumers list. + // By construction, initial consumers will necessarily become consumers + // again later. consumers.swap(priorConsumers); - // Make producers generate the required data + // 4) Make producers generate the required data. + // Producers are special nodes that generate data on demand. for (const auto& requiredProducer : requiredProducers) { requiredProducer->getOperator()->updateConsummerProducer(); mStaticSchedule.back().push_back(requiredProducer); } - // find runnable consumers + // 5) Find runnable consumers. + // A consumer is runnable if the required data is available for all of + // its inputs. At this point, not all consumers are necessarily + // runnable because some may depend on the execution of others (when + // there is multiple successive priors for example). std::set<std::shared_ptr<Node>> runnableConsumers; if (verbose) printf("Updated list of consumers:\n"); for (const auto& consumer : consumers) { @@ -178,30 +180,31 @@ void Aidge::SequentialScheduler::generateScheduling(bool verbose) { } } - // Push consumers in the list of nodes to run and update the consumer producer system + // 5) If not consumer is runnable, it is a stop condition! + if (runnableConsumers.empty()) { + if (verbose) printf("********************\n"); + // No consumer is runnable: some required data is missing for all of + // them. There is two possibilities: + // - At least one required data source is exhausted, which may be + // an expected stop condition. + // - There is a deadlock between consumers, if some one is waiting + // for data from the other and reciprocally. + break; + } + + // 6) Push runnable consumers in the list of nodes to run and update the + // consumer producer system. + // At this point, simultaneously runnable consumers have no data + // dependency and could be run in parallel! for (const auto& runnable : runnableConsumers) { if (verbose) printf("Runnable: %s\n", namePtrTable[runnable].c_str()); runnable->getOperator()->updateConsummerProducer(); mStaticSchedule.back().push_back(runnable); } - if (runnableConsumers.empty()) { - if (std::find(frozenConsumers.begin(), frozenConsumers.end(), consumers) == frozenConsumers.end()) { - frozenConsumers.push_back(consumers); - } - else { - break; - } - } - else { - frozenConsumers.clear(); - } - - // update producers and consumers list + // 7) Update consumers list if (verbose) printf("Updating producer and consumer lists...\n"); - const auto oldConsumers = consumers; - - for (const auto& consumer : oldConsumers) { + for (const auto& consumer : runnableConsumers) { if (verbose) { printf("\t- consumer: %s\n\t\tC/R:\t", namePtrTable[consumer].c_str()); @@ -219,6 +222,9 @@ void Aidge::SequentialScheduler::generateScheduling(bool verbose) { printf("\n"); } + // 7.1) If the current consumer has still data to consume, it will + // be put back in the consumers list once the remaining consumers + // have been exhausted. bool isStillConsumer = false; for (IOIndex_t inputIdx = 0; inputIdx < consumer->nbInputs(); ++inputIdx) { if (consumer->getOperator()->getNbConsumedData(inputIdx) < @@ -233,7 +239,24 @@ void Aidge::SequentialScheduler::generateScheduling(bool verbose) { } } + // 7.2) If the current consumer becomes a producer for other nodes, + // its childs become consumers. + bool isProducer = false; for (IOIndex_t outId = 0; outId < consumer->nbOutputs(); ++outId) { + for (const auto child : consumer->getChildren(outId)) { + if (child) { + IOIndex_t inputIdx = 0; + for (const auto childParent : child->getParents()) { + if (childParent == consumer) { + if (consumer->getOperator()->getNbProducedData(outId) > child->getOperator()->getNbConsumedData(inputIdx)) { + isProducer = true; + } + } + ++inputIdx; + } + } + } +/* if (consumer->getOperator()->getNbProducedData(outId) > 0) { if (verbose) printf(" also producer\n"); // make sure consumer is also a producer @@ -243,22 +266,29 @@ void Aidge::SequentialScheduler::generateScheduling(bool verbose) { consumers.insert(newConsumers.cbegin(), newConsumers.cend()); break; } +*/ } - if (runnableConsumers.find(consumer) != runnableConsumers.end()) { - // If consumer was run, remove it from the consumers list for - // now - consumers.erase(consumer); - if (isStillConsumer) { - // If there is still data to consume, the consumer will be - // run AFTER the other remaining consumers - // (= non-greedy consumers) - stillConsumers.insert(consumer); - } + consumers.erase(consumer); + + if (isProducer) { + if (verbose) printf(" also producer\n"); + // make sure consumer is also a producer + producers.insert(consumer); + + const auto& newConsumers = getConsumers({consumer}); + consumers.insert(newConsumers.cbegin(), newConsumers.cend()); + } + + if (isStillConsumer) { + // If there is still data to consume, the consumer will be + // run AFTER the other remaining consumers + // (= non-greedy consumers) + stillConsumers.insert(consumer); } } - // If there is no more consumers, swap with possible "still consumers" + // 8) If there is no more consumers, swap with possible "still consumers" // This ensures that the "non-greedy" consumer behavior if (consumers.empty()) { consumers.swap(stillConsumers); @@ -270,7 +300,7 @@ void Aidge::SequentialScheduler::generateScheduling(bool verbose) { if (verbose) { if (!consumers.empty()) { - printf("*** Frozen state ***\n"); + printf("/!\\ Remaining consumers: possible dead-lock\n"); printf("********************\n"); } }