/************************************************************************************* Grid physics library, www.github.com/paboyle/Grid Source file: extras/Hadrons/VirtualMachine.cc Copyright (C) 2015-2018 Author: Antonin Portelli This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. See the full license in the file "LICENSE" in the top level distribution directory *************************************************************************************/ /* END LEGAL */ #include #include #include using namespace Grid; using namespace QCD; using namespace Hadrons; /****************************************************************************** * VirtualMachine implementation * ******************************************************************************/ // trajectory counter ////////////////////////////////////////////////////////// void VirtualMachine::setTrajectory(const unsigned int traj) { traj_ = traj; } unsigned int VirtualMachine::getTrajectory(void) const { return traj_; } // module management /////////////////////////////////////////////////////////// void VirtualMachine::pushModule(VirtualMachine::ModPt &pt) { std::string name = pt->getName(); if (!hasModule(name)) { std::vector inputAddress; unsigned int address; ModuleInfo m; // module registration ------------------------------------------------- m.data = std::move(pt); m.type = typeIdPt(*m.data.get()); m.name = name; // input dependencies for (auto &in: m.data->getInput()) { if (!env().hasObject(in)) { // if object does not exist, add it with no creator module env().addObject(in , -1); } m.input.push_back(env().getObjectAddress(in)); } // reference dependencies for (auto &ref: m.data->getReference()) { if (!env().hasObject(ref)) { // if object does not exist, add it with no creator module env().addObject(ref , -1); } m.input.push_back(env().getObjectAddress(ref)); } auto inCopy = m.input; // if module has inputs with references, they need to be added as // an input for (auto &in: inCopy) { int inm = env().getObjectModule(in); if (inm > 0) { if (getModule(inm)->getReference().size() > 0) { for (auto &rin: getModule(inm)->getReference()) { m.input.push_back(env().getObjectAddress(rin)); } } } } module_.push_back(std::move(m)); address = static_cast(module_.size() - 1); moduleAddress_[name] = address; // connecting outputs to potential inputs ------------------------------ for (auto &out: getModule(address)->getOutput()) { if (!env().hasObject(out)) { // output does not exists, add it env().addObject(out, address); module_[address].output.push_back(env().getObjectAddress(out)); } else { if (env().getObjectModule(env().getObjectAddress(out)) < 0) { // output exists but without creator, correct it env().setObjectModule(env().getObjectAddress(out), address); } else { // output already fully registered, error HADRON_ERROR(Definition, "object '" + out + "' is already produced by module '" + module_[env().getObjectModule(out)].name + "' (while pushing module '" + name + "')"); } if (getModule(address)->getReference().size() > 0) { // module has references, dependency should be propagated // to children modules; find module with `out` as an input // and add references to their input auto pred = [this, out](const ModuleInfo &n) { auto &in = n.input; auto it = std::find(in.begin(), in.end(), env().getObjectAddress(out)); return (it != in.end()); }; auto it = std::find_if(module_.begin(), module_.end(), pred); while (it != module_.end()) { for (auto &ref: getModule(address)->getReference()) { it->input.push_back(env().getObjectAddress(ref)); } it = std::find_if(++it, module_.end(), pred); } } } } graphOutdated_ = true; memoryProfileOutdated_ = true; } else { HADRON_ERROR(Definition, "module '" + name + "' already exists"); } } unsigned int VirtualMachine::getNModule(void) const { return module_.size(); } void VirtualMachine::createModule(const std::string name, const std::string type, XmlReader &reader) { auto &factory = ModuleFactory::getInstance(); auto pt = factory.create(type, name); pt->parseParameters(reader, "options"); pushModule(pt); } ModuleBase * VirtualMachine::getModule(const unsigned int address) const { if (hasModule(address)) { return module_[address].data.get(); } else { HADRON_ERROR(Definition, "no module with address " + std::to_string(address)); } } ModuleBase * VirtualMachine::getModule(const std::string name) const { return getModule(getModuleAddress(name)); } unsigned int VirtualMachine::getModuleAddress(const std::string name) const { if (hasModule(name)) { return moduleAddress_.at(name); } else { HADRON_ERROR(Definition, "no module with name '" + name + "'"); } } std::string VirtualMachine::getModuleName(const unsigned int address) const { if (hasModule(address)) { return module_[address].name; } else { HADRON_ERROR(Definition, "no module with address " + std::to_string(address)); } } std::string VirtualMachine::getModuleType(const unsigned int address) const { if (hasModule(address)) { return typeName(module_[address].type); } else { HADRON_ERROR(Definition, "no module with address " + std::to_string(address)); } } std::string VirtualMachine::getModuleType(const std::string name) const { return getModuleType(getModuleAddress(name)); } std::string VirtualMachine::getModuleNamespace(const unsigned int address) const { std::string type = getModuleType(address), ns; auto pos2 = type.rfind("::"); auto pos1 = type.rfind("::", pos2 - 2); return type.substr(pos1 + 2, pos2 - pos1 - 2); } std::string VirtualMachine::getModuleNamespace(const std::string name) const { return getModuleNamespace(getModuleAddress(name)); } bool VirtualMachine::hasModule(const unsigned int address) const { return (address < module_.size()); } bool VirtualMachine::hasModule(const std::string name) const { return (moduleAddress_.find(name) != moduleAddress_.end()); } // print VM content //////////////////////////////////////////////////////////// void VirtualMachine::printContent(void) const { LOG(Debug) << "Modules: " << std::endl; for (unsigned int i = 0; i < module_.size(); ++i) { LOG(Debug) << std::setw(4) << i << ": " << getModuleName(i) << std::endl; } } // module graph //////////////////////////////////////////////////////////////// Graph VirtualMachine::getModuleGraph(void) { if (graphOutdated_) { makeModuleGraph(); graphOutdated_ = false; } return graph_; } void VirtualMachine::makeModuleGraph(void) { Graph graph; // create vertices for (unsigned int m = 0; m < module_.size(); ++m) { graph.addVertex(m); } // create edges for (unsigned int m = 0; m < module_.size(); ++m) { for (auto &in: module_[m].input) { int min = env().getObjectModule(in); if (min < 0) { HADRON_ERROR(Definition, "object with address " + std::to_string(in) + " is not produced by any module"); } else { graph.addEdge(min, m); } } } graph_ = graph; } // dump GraphViz graph ///////////////////////////////////////////////////////// void VirtualMachine::dumpModuleGraph(std::ostream &out) { makeModuleGraph(); out << "digraph hadrons {" << std::endl; out << "node [shape=record, fontname=\"Courier\", fontsize=\"11\"];" << std::endl; out << "graph [fontname = \"Courier\", fontsize=\"11\"];" << std::endl; out << "edge [fontname = \"Courier\", fontsize=\"11\"];"<< std::endl; for (unsigned int m = 0; m < module_.size(); ++m) { } for (unsigned int m = 0; m < module_.size(); ++m) { for (auto &in: module_[m].input) { int min = env().getObjectModule(in); out << min << " -> " << m << " [ label = \"" << env().getObjectName(in) << "\" ];" << std::endl; } } for (unsigned int m = 0; m < module_.size(); ++m) { out << m << " [ label = \"{ " << getModule(m)->getRegisteredName() << " | " << getModuleName(m) << "}\" ];" << std::endl; } out << "}\n" << std::endl; } void VirtualMachine::dumpModuleGraph(void) { dumpModuleGraph(std::cout); } void VirtualMachine::dumpModuleGraph(const std::string filename) { std::ofstream f(filename); dumpModuleGraph(f); } // memory profile ////////////////////////////////////////////////////////////// const VirtualMachine::MemoryProfile & VirtualMachine::getMemoryProfile(void) { if (memoryProfileOutdated_) { makeMemoryProfile(); memoryProfileOutdated_ = false; } return profile_; } void VirtualMachine::makeMemoryProfile(void) { bool protect = env().objectsProtected(); bool hmsg = HadronsLogMessage.isActive(); bool gmsg = GridLogMessage.isActive(); bool err = HadronsLogError.isActive(); auto program = getModuleGraph().topoSort(); resetProfile(); profile_.module.resize(getNModule()); env().protectObjects(false); GridLogMessage.Active(false); HadronsLogMessage.Active(false); for (auto it = program.rbegin(); it != program.rend(); ++it) { auto a = *it; if (profile_.module[a].empty()) { LOG(Debug) << "Profiling memory for module '" << module_[a].name << "' (" << a << ")..." << std::endl; memoryProfile(a); env().freeAll(); } } env().protectObjects(protect); GridLogMessage.Active(gmsg); HadronsLogMessage.Active(hmsg); LOG(Debug) << "Memory profile:" << std::endl; LOG(Debug) << "----------------" << std::endl; for (unsigned int a = 0; a < profile_.module.size(); ++a) { LOG(Debug) << getModuleName(a) << " (" << a << ")" << std::endl; for (auto &o: profile_.module[a]) { LOG(Debug) << "|__ " << env().getObjectName(o.first) << " (" << sizeString(o.second) << ")" << std::endl; } LOG(Debug) << std::endl; } LOG(Debug) << "----------------" << std::endl; } void VirtualMachine::resetProfile(void) { profile_.module.clear(); profile_.object.clear(); } void VirtualMachine::resizeProfile(void) { if (env().getMaxAddress() > profile_.object.size()) { MemoryPrint empty; empty.size = 0; empty.module = -1; profile_.object.resize(env().getMaxAddress(), empty); } } void VirtualMachine::updateProfile(const unsigned int address) { resizeProfile(); for (unsigned int a = 0; a < env().getMaxAddress(); ++a) { if (env().hasCreatedObject(a) and (profile_.object[a].module == -1)) { profile_.object[a].size = env().getObjectSize(a); profile_.object[a].storage = env().getObjectStorage(a); profile_.object[a].module = address; profile_.module[address][a] = profile_.object[a].size; if (env().getObjectModule(a) < 0) { env().setObjectModule(a, address); } } } } void VirtualMachine::cleanEnvironment(void) { resizeProfile(); for (unsigned int a = 0; a < env().getMaxAddress(); ++a) { if (env().hasCreatedObject(a) and (profile_.object[a].module == -1)) { env().freeObject(a); } } } void VirtualMachine::memoryProfile(const unsigned int address) { auto m = getModule(address); LOG(Debug) << "Setting up module '" << m->getName() << "' (" << address << ")..." << std::endl; try { m->setup(); updateProfile(address); } catch (Exceptions::Definition &) { cleanEnvironment(); for (auto &in: m->getInput()) { if (!env().hasCreatedObject(in)) { memoryProfile(env().getObjectModule(in)); } } for (auto &ref: m->getReference()) { if (!env().hasCreatedObject(ref)) { memoryProfile(env().getObjectModule(ref)); } } m->setup(); updateProfile(address); } } void VirtualMachine::memoryProfile(const std::string name) { memoryProfile(getModuleAddress(name)); } // garbage collector /////////////////////////////////////////////////////////// VirtualMachine::GarbageSchedule VirtualMachine::makeGarbageSchedule(const Program &p) const { GarbageSchedule freeProg; freeProg.resize(p.size()); for (unsigned int a = 0; a < env().getMaxAddress(); ++a) { if (env().getObjectStorage(a) == Environment::Storage::temporary) { auto it = std::find(p.begin(), p.end(), env().getObjectModule(a)); if (it != p.end()) { freeProg[std::distance(p.begin(), it)].insert(a); } } else if (env().getObjectStorage(a) == Environment::Storage::object) { auto pred = [a, this](const unsigned int b) { auto &in = module_[b].input; auto it = std::find(in.begin(), in.end(), a); return (it != in.end()) or (b == env().getObjectModule(a)); }; auto it = std::find_if(p.rbegin(), p.rend(), pred); if (it != p.rend()) { freeProg[std::distance(it, p.rend()) - 1].insert(a); } } } return freeProg; } // high-water memory function ////////////////////////////////////////////////// VirtualMachine::Size VirtualMachine::memoryNeeded(const Program &p) { const MemoryProfile &profile = getMemoryProfile(); GarbageSchedule freep = makeGarbageSchedule(p); Size current = 0, max = 0; for (unsigned int i = 0; i < p.size(); ++i) { for (auto &o: profile.module[p[i]]) { current += o.second; } max = std::max(current, max); for (auto &o: freep[i]) { current -= profile.object[o].size; } } return max; } // genetic scheduler /////////////////////////////////////////////////////////// VirtualMachine::Program VirtualMachine::schedule(const GeneticPar &par) { typedef GeneticScheduler Scheduler; auto graph = getModuleGraph(); //constrained topological sort using a genetic algorithm LOG(Message) << "Scheduling computation..." << std::endl; LOG(Message) << " #module= " << graph.size() << std::endl; LOG(Message) << " population size= " << par.popSize << std::endl; LOG(Message) << " max. generation= " << par.maxGen << std::endl; LOG(Message) << " max. cst. generation= " << par.maxCstGen << std::endl; LOG(Message) << " mutation rate= " << par.mutationRate << std::endl; unsigned int k = 0, gen, prevPeak, nCstPeak = 0; std::random_device rd; Scheduler::Parameters gpar; gpar.popSize = par.popSize; gpar.mutationRate = par.mutationRate; gpar.seed = rd(); CartesianCommunicator::BroadcastWorld(0, &(gpar.seed), sizeof(gpar.seed)); Scheduler::ObjFunc memPeak = [this](const Program &p)->Size { return memoryNeeded(p); }; Scheduler scheduler(graph, memPeak, gpar); gen = 0; do { //LOG(Debug) << "Generation " << gen << ":" << std::endl; scheduler.nextGeneration(); if (gen != 0) { if (prevPeak == scheduler.getMinValue()) { nCstPeak++; } else { nCstPeak = 0; } } prevPeak = scheduler.getMinValue(); if (gen % 10 == 0) { LOG(Iterative) << "Generation " << gen << ": " << sizeString(scheduler.getMinValue()) << std::endl; } gen++; } while ((gen < par.maxGen) and (nCstPeak < par.maxCstGen)); return scheduler.getMinSchedule(); } // general execution /////////////////////////////////////////////////////////// #define BIG_SEP "===============" #define SEP "---------------" #define MEM_MSG(size) sizeString(size) void VirtualMachine::executeProgram(const Program &p) const { Size memPeak = 0, sizeBefore, sizeAfter; GarbageSchedule freeProg; // build garbage collection schedule LOG(Debug) << "Building garbage collection schedule..." << std::endl; freeProg = makeGarbageSchedule(p); for (unsigned int i = 0; i < freeProg.size(); ++i) { LOG(Debug) << std::setw(4) << i + 1 << ": ["; for (auto &a: freeProg[i]) { std::cout << env().getObjectName(a) << " "; } std::cout << "]" << std::endl; } // program execution LOG(Debug) << "Executing program..." << std::endl; for (unsigned int i = 0; i < p.size(); ++i) { // execute module LOG(Message) << SEP << " Measurement step " << i + 1 << "/" << p.size() << " (module '" << module_[p[i]].name << "') " << SEP << std::endl; (*module_[p[i]].data)(); sizeBefore = env().getTotalSize(); // print used memory after execution LOG(Message) << "Allocated objects: " << MEM_MSG(sizeBefore) << std::endl; if (sizeBefore > memPeak) { memPeak = sizeBefore; } // garbage collection for step i LOG(Message) << "Garbage collection..." << std::endl; for (auto &j: freeProg[i]) { env().freeObject(j); } // print used memory after garbage collection if necessary sizeAfter = env().getTotalSize(); if (sizeBefore != sizeAfter) { LOG(Message) << "Allocated objects: " << MEM_MSG(sizeAfter) << std::endl; } else { LOG(Message) << "Nothing to free" << std::endl; } } } void VirtualMachine::executeProgram(const std::vector &p) const { Program pAddress; for (auto &n: p) { pAddress.push_back(getModuleAddress(n)); } executeProgram(pAddress); }