Skip to content

Commit 99baa59

Browse files
committed
Reuse same instances in hybrid generator
1 parent d0fec95 commit 99baa59

3 files changed

Lines changed: 100 additions & 26 deletions

File tree

‎Generators/include/Generators/GeneratorHybrid.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ class GeneratorHybrid : public Generator
8181
std::vector<std::string> mGens;
8282
std::vector<std::string> mConfigs;
8383
std::vector<std::string> mConfsPythia8;
84+
// canonical description of how each configured slot is set up (name, configuration and
85+
// triggers). Slots sharing a signature are served by one single generator instance
86+
std::vector<std::string> mGenSignatures;
87+
// Maps a configured generator onto the generator instance in 'gens' serving it.
88+
// In sequential mode, slots configured in exactly the same way share a single instance by default
89+
std::vector<int> mSlotToInstance;
8490

8591
std::vector<bool> mGenIsInitialized;
8692

‎Generators/include/Generators/GeneratorHybridParam.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ struct GeneratorHybridParam : public o2::conf::ConfigurableParamHelper<Generator
3333
bool randomize = false; // randomize the order of the generators, if not generator using fractions
3434
int num_workers = 1; // number of threads available for asyn/parallel event generation
3535
bool switchExtToHybrid = false; // force external generator to be executed as hybrid mode, useful for Hyperloop MCGEN
36+
bool reuseIdenticalGenerators = true; // in sequential mode, generators configured in exactly the same way share a single instance by default
3637
O2ParamDef(GeneratorHybridParam, "GeneratorHybrid");
3738
};
3839

‎Generators/src/GeneratorHybrid.cxx‎

Lines changed: 93 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "Generators/GeneratorHybrid.h"
1313
#include <fairlogger/Logger.h>
1414
#include <algorithm>
15+
#include <unordered_map>
1516
#include <tbb/concurrent_queue.h>
1617
#include <tbb/task_arena.h>
1718
#include <tbb/parallel_for.h>
@@ -30,6 +31,20 @@ GeneratorHybrid& GeneratorHybrid::Instance(const std::string& inputgens)
3031
return instance;
3132
}
3233

34+
namespace
35+
{
36+
// Canonical description of a parsed generator configuration
37+
template <typename T>
38+
std::string canonicalConfig(T const* conf)
39+
{
40+
if (!conf) {
41+
return {};
42+
}
43+
auto json = TBufferJSON::ToJSON(conf);
44+
return std::string(json.Data(), json.Length());
45+
}
46+
} // namespace
47+
3348
GeneratorHybrid::GeneratorHybrid(const std::string& inputgens)
3449
{
3550
// This generator has trivial unit conversions
@@ -62,7 +77,7 @@ GeneratorHybrid::GeneratorHybrid(const std::string& inputgens)
6277
exit(1);
6378
}
6479
mRandomize = GeneratorHybridParam::Instance().randomize;
65-
if (mConfigs.size() != mInputGens.size()) {
80+
if (mConfigs.size() != mInputGens.size() || mGenSignatures.size() != mInputGens.size()) {
6681
LOG(fatal) << "Number of configurations does not match the number of generators";
6782
exit(1);
6883
}
@@ -90,11 +105,30 @@ GeneratorHybrid::GeneratorHybrid(const std::string& inputgens)
90105
return;
91106
}
92107
}
108+
// Only in sequential mode, generators which are configured in exactly the same way are
109+
// instantiated only once and the corresponding slots are served by that single instance.
110+
// If num_workers <= 1, the reuse of identical generators is automatically disabled.
111+
const bool reuseIdentical = (mGenerationMode == GenMode::kSeq) && GeneratorHybridParam::Instance().reuseIdenticalGenerators && GeneratorHybridParam::Instance().num_workers <= 1;
112+
std::unordered_map<std::string, int> signatureToInstance;
93113
for (auto gen : mInputGens) {
94114
// Search if the generator name is inside generatorNames (which is a vector of strings)
95115
LOG(info) << "Checking if generator " << gen << " is in the list of available generators \n";
96116
if (std::find(generatorNames.begin(), generatorNames.end(), gen) != generatorNames.end()) {
97117
LOG(info) << "Found generator " << gen << " in the list of available generators \n";
118+
if (reuseIdentical) {
119+
auto const& signature = mGenSignatures[index];
120+
auto known = signatureToInstance.find(signature);
121+
if (known != signatureToInstance.end()) {
122+
LOG(info) << "Generator " << gen << " (slot " << index << ") is configured exactly as instance "
123+
<< known->second << ": reusing it instead of creating a new one";
124+
mSlotToInstance.push_back(known->second);
125+
mGens.push_back(gen);
126+
index++;
127+
continue;
128+
}
129+
// the instance about to be created will be the last one of 'gens'
130+
signatureToInstance[signature] = gens.size();
131+
}
98132
if (gen.compare("boxgen") == 0) {
99133
if (mConfigs[index].compare("") == 0) {
100134
gens.push_back(std::make_shared<o2::eventgen::BoxGenerator>());
@@ -184,8 +218,17 @@ GeneratorHybrid::GeneratorHybrid(const std::string& inputgens)
184218
LOG(fatal) << "Generator " << gen << " not found in the list of available generators \n";
185219
exit(1);
186220
}
221+
// a new instance has just been created for this slot
222+
mSlotToInstance.push_back(gens.size() - 1);
187223
index++;
188224
}
225+
LOG(info) << "Hybrid generator: " << mInputGens.size() << " configured generator(s) served by "
226+
<< gens.size() << " generator instance(s)";
227+
if (reuseIdentical) {
228+
for (size_t slot = 0; slot < mSlotToInstance.size(); ++slot) {
229+
LOG(info) << " slot " << slot << " (" << mInputGens[slot] << ") -> instance " << mSlotToInstance[slot];
230+
}
231+
}
189232
}
190233

191234
GeneratorHybrid::~GeneratorHybrid()
@@ -197,29 +240,39 @@ GeneratorHybrid::~GeneratorHybrid()
197240
Bool_t GeneratorHybrid::Init()
198241
{
199242
// init all sub-gens
243+
mGenIsInitialized.resize(gens.size(), false);
200244
int count = 0;
201245
for (auto& gen : mGens) {
246+
// slots sharing one instance must configure, initialise and equip it only once
247+
const auto instance = mSlotToInstance[count];
248+
if (mGenIsInitialized[instance]) {
249+
LOG(info) << "Generator " << gen << " (slot " << count << ") is served by the already initialized instance " << instance;
250+
addSubGenerator(count, gen);
251+
count++;
252+
continue;
253+
}
202254
if (gen == "pythia8pp") {
203255
auto config = std::string(std::getenv("O2_ROOT")) + "/share/Generators/egconfig/pythia8_inel.cfg";
204256
LOG(info) << "Setting \'Pythia8\' base configuration: " << config << std::endl;
205-
dynamic_cast<o2::eventgen::GeneratorPythia8*>(gens[count].get())->setConfig(config);
257+
dynamic_cast<o2::eventgen::GeneratorPythia8*>(gens[instance].get())->setConfig(config);
206258
} else if (gen == "pythia8hf") {
207259
auto config = std::string(std::getenv("O2_ROOT")) + "/share/Generators/egconfig/pythia8_hf.cfg";
208260
LOG(info) << "Setting \'Pythia8\' base configuration: " << config << std::endl;
209-
dynamic_cast<o2::eventgen::GeneratorPythia8*>(gens[count].get())->setConfig(config);
261+
dynamic_cast<o2::eventgen::GeneratorPythia8*>(gens[instance].get())->setConfig(config);
210262
} else if (gen == "pythia8hi") {
211263
auto config = std::string(std::getenv("O2_ROOT")) + "/share/Generators/egconfig/pythia8_hi.cfg";
212264
LOG(info) << "Setting \'Pythia8\' base configuration: " << config << std::endl;
213-
dynamic_cast<o2::eventgen::GeneratorPythia8*>(gens[count].get())->setConfig(config);
265+
dynamic_cast<o2::eventgen::GeneratorPythia8*>(gens[instance].get())->setConfig(config);
214266
} else if (gen == "pythia8powheg") {
215267
auto config = std::string(std::getenv("O2_ROOT")) + "/share/Generators/egconfig/pythia8_powheg.cfg";
216268
LOG(info) << "Setting \'Pythia8\' base configuration: " << config << std::endl;
217-
dynamic_cast<o2::eventgen::GeneratorPythia8*>(gens[count].get())->setConfig(config);
269+
dynamic_cast<o2::eventgen::GeneratorPythia8*>(gens[instance].get())->setConfig(config);
218270
}
219-
gens[count]->Init(); // TODO: move this to multi-threaded
271+
gens[instance]->Init(); // TODO: move this to multi-threaded
272+
mGenIsInitialized[instance] = true;
220273
addSubGenerator(count, gen);
221274
if (mTriggerModes[count] != o2::eventgen::Generator::kTriggerOFF) {
222-
gens[count]->setTriggerMode(mTriggerModes[count]);
275+
gens[instance]->setTriggerMode(mTriggerModes[count]);
223276
LOG(info) << "Setting Trigger mode of generator " << gen << " to: " << mTriggerModes[count];
224277
o2::eventgen::Trigger trigger = nullptr;
225278
o2::eventgen::DeepTrigger deeptrigger = nullptr;
@@ -244,9 +297,9 @@ Bool_t GeneratorHybrid::Init()
244297
LOG(info) << "Trigger " << trg << " of generator " << gen << " successfully set";
245298
}
246299
if (trigger) {
247-
gens[count]->addTrigger(trigger);
300+
gens[instance]->addTrigger(trigger);
248301
} else {
249-
gens[count]->addDeepTrigger(deeptrigger);
302+
gens[instance]->addDeepTrigger(deeptrigger);
250303
}
251304
}
252305
}
@@ -293,7 +346,6 @@ Bool_t GeneratorHybrid::Init()
293346
LOG(info) << "Generators will be used in sequence, following provided fractions";
294347
}
295348

296-
mGenIsInitialized.resize(gens.size(), false);
297349
if (mGenerationMode == GenMode::kParallel) {
298350
// in parallel mode we just use one queue --> collaboration
299351
mResultQueue.resize(1);
@@ -411,24 +463,15 @@ bool GeneratorHybrid::generateEvent()
411463
bool GeneratorHybrid::importParticles()
412464
{
413465
int genIndex = -1;
414-
std::vector<int> subGenIndex = {};
415466
if (mIndex == -1) {
416467
// this means parallel mode ---> we have a common queue
417468
mResultQueue[0].pop(genIndex);
418-
} else {
419-
// need to pop from a particular queue
420-
if (!mCocktailMode) {
421-
mResultQueue[mIndex].pop(genIndex);
422-
} else {
423-
// in cocktail mode we need to pop from the group queue
424-
subGenIndex.resize(mGroups[mIndex].size());
425-
for (size_t pos = 0; pos < mGroups[mIndex].size(); ++pos) {
426-
int subIndex = mGroups[mIndex][pos];
427-
LOG(info) << "Getting generator " << mGens[subIndex] << " from cocktail group " << mIndex;
428-
mResultQueue[subIndex].pop(subGenIndex[pos]);
429-
}
430-
}
469+
} else if (!mCocktailMode) {
470+
// need to pop from the queue of the instance serving this generator slot
471+
mResultQueue[mSlotToInstance[mIndex]].pop(genIndex);
431472
}
473+
// in cocktail mode the events of the group members are popped one by one while they are
474+
// imported, so that one instance shared by two members of the same group can serve both of them.
432475

433476
auto unit_transformer = [](auto& p, auto pos_unit, auto time_unit, auto en_unit, auto mom_unit) {
434477
p.SetMomentum(p.Px() * mom_unit, p.Py() * mom_unit, p.Pz() * mom_unit, p.Energy() * en_unit);
@@ -456,9 +499,13 @@ bool GeneratorHybrid::importParticles()
456499
// this is important when using event pools where the full header information is forwarded from the generator
457500
// otherwise some events might have mixed header information from different generators
458501
mMCEventHeader.Reset();
459-
if (mCocktailMode) {
502+
if (mCocktailMode && mIndex >= 0) {
460503
// in cocktail mode we need to merge the particles from the different generators
461-
for (auto subIndex : subGenIndex) {
504+
for (size_t pos = 0; pos < mGroups[mIndex].size(); ++pos) {
505+
const auto slotIndex = mGroups[mIndex][pos];
506+
LOG(info) << "Getting generator " << mGens[slotIndex] << " from cocktail group " << mIndex;
507+
int subIndex = -1;
508+
mResultQueue[mSlotToInstance[slotIndex]].pop(subIndex);
462509
LOG(info) << "Importing particles for task " << subIndex;
463510
auto subParticles = gens[subIndex]->getParticles();
464511

@@ -550,27 +597,32 @@ Bool_t GeneratorHybrid::confSetter(const auto& gen)
550597
{
551598
std::string name = gen["name"].GetString();
552599
mInputGens.push_back(name);
600+
std::string cfgSignature{"default"}; // canonical description of the configuration parsed below
553601
if (gen.HasMember("config")) {
554602
if (name == "boxgen") {
555603
const auto& boxconf = gen["config"];
556604
auto boxConfig = TBufferJSON::FromJSON<o2::eventgen::BoxGenConfig>(jsonValueToString(boxconf).c_str());
557605
mBoxGenConfigs.push_back(std::move(boxConfig));
558606
mConfigs.push_back("boxgen_" + std::to_string(mBoxGenConfigs.size() - 1));
607+
cfgSignature = canonicalConfig(mBoxGenConfigs.back().get());
559608
} else if (name == "pythia8") {
560609
const auto& pythia8conf = gen["config"];
561610
auto pythia8Config = TBufferJSON::FromJSON<o2::eventgen::Pythia8GenConfig>(jsonValueToString(pythia8conf).c_str());
562611
mPythia8GenConfigs.push_back(std::move(pythia8Config));
563612
mConfigs.push_back("pythia8_" + std::to_string(mPythia8GenConfigs.size() - 1));
613+
cfgSignature = canonicalConfig(mPythia8GenConfigs.back().get());
564614
} else if (name == "evtpool") {
565615
const auto& o2kineconf = gen["config"];
566616
auto poolConfig = TBufferJSON::FromJSON<o2::eventgen::EventPoolGenConfig>(jsonValueToString(o2kineconf).c_str());
567617
mEventPoolConfigs.push_back(*poolConfig);
568618
mConfigs.push_back("evtpool_" + std::to_string(mEventPoolConfigs.size() - 1));
619+
cfgSignature = canonicalConfig(&mEventPoolConfigs.back());
569620
} else if (name == "external") {
570621
const auto& extconf = gen["config"];
571622
auto extConfig = TBufferJSON::FromJSON<o2::eventgen::ExternalGenConfig>(jsonValueToString(extconf).c_str());
572623
mExternalGenConfigs.push_back(std::move(extConfig));
573624
mConfigs.push_back("external_" + std::to_string(mExternalGenConfigs.size() - 1));
625+
cfgSignature = canonicalConfig(mExternalGenConfigs.back().get());
574626
} else if (name == "hepmc") {
575627
const auto& genconf = gen["config"];
576628
const auto& cmdconf = genconf["configcmd"];
@@ -580,6 +632,7 @@ Bool_t GeneratorHybrid::confSetter(const auto& gen)
580632
mFileOrCmdGenConfigs.push_back(std::move(cmdConfig));
581633
mHepMCGenConfigs.push_back(std::move(hepmcConfig));
582634
mConfigs.push_back("hepmc_" + std::to_string(mFileOrCmdGenConfigs.size() - 1));
635+
cfgSignature = canonicalConfig(mFileOrCmdGenConfigs.back().get()) + canonicalConfig(mHepMCGenConfigs.back().get());
583636
} else {
584637
mConfigs.push_back("");
585638
}
@@ -591,6 +644,12 @@ Bool_t GeneratorHybrid::confSetter(const auto& gen)
591644
mConfigs.push_back("");
592645
}
593646
}
647+
// Slots are served by one shared instance only when their signatures match, so a
648+
// configuration which could not be serialised must never compare equal to another one.
649+
if (cfgSignature.empty()) {
650+
cfgSignature = "slot:" + std::to_string(mInputGens.size() - 1);
651+
}
652+
mGenSignatures.push_back(name + "|cfg:" + cfgSignature);
594653
if (gen.HasMember("triggers")) {
595654
const auto& trigger = gen["triggers"];
596655
auto trigger_specs = [this, &trigger]() {
@@ -653,6 +712,14 @@ Bool_t GeneratorHybrid::confSetter(const auto& gen)
653712
mTriggerMacros.push_back({""});
654713
mTriggerFuncs.push_back({""});
655714
}
715+
// the triggers are attached to the generator instance, hence they are part of its identity
716+
mGenSignatures.back() += "|trg:" + std::to_string(static_cast<int>(mTriggerModes.back()));
717+
for (size_t trg = 0; trg < mTriggerMacros.back().size(); ++trg) {
718+
mGenSignatures.back() += "|" + mTriggerMacros.back()[trg];
719+
if (trg < mTriggerFuncs.back().size()) {
720+
mGenSignatures.back() += ":" + mTriggerFuncs.back()[trg];
721+
}
722+
}
656723
return true;
657724
}
658725

0 commit comments

Comments
 (0)