Skip to content

Commit ca5b99c

Browse files
committed
HybridGenerator: Asyncronous + parallel event generation
This introduces: * asyncronous event generation * possibility for parallel event generation This is useful for: * hiding latency (IO) of certain generators * decoupling the actual work from the call sequence into HybridGenerator * collaboration from multiple clones of the same generator to generate a certain number of events The implementation relies on tbb::task_arena and input/output queues for decoupling the task_arena from the HybridGenerator thread. Small adjustments to seeding of Pythia8 in order to avoid same seeds in multiple parallel Pythia instances.
1 parent f4f8f43 commit ca5b99c

File tree

7 files changed

+169
-36
lines changed

7 files changed

+169
-36
lines changed

Generators/include/Generators/Generator.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ class Generator : public FairGenerator
141141
/** lorentz boost data members **/
142142
Double_t mBoost;
143143

144+
// a unique generator instance counter
145+
// this can be used to make sure no two generator instances have the same seed etc.
146+
static std::atomic<int> InstanceCounter;
147+
int mThisInstanceID = 0;
148+
144149
private:
145150
void updateSubGeneratorInformation(o2::dataformats::MCEventHeader* header) const;
146151

Generators/include/Generators/GeneratorHybrid.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
#include <rapidjson/writer.h>
4040
#include "TBufferJSON.h"
4141

42+
#include <tbb/concurrent_queue.h>
43+
#include <tbb/task_arena.h>
44+
#include <iostream>
45+
#include <thread>
46+
#include <atomic>
47+
4248
namespace o2
4349
{
4450
namespace eventgen
@@ -84,6 +90,24 @@ class GeneratorHybrid : public Generator
8490
int mseqCounter = 0;
8591
int mCurrentFraction = 0;
8692
int mIndex = 0;
93+
94+
// Create a task arena with a specified number of threads
95+
std::thread mTBBTaskPoolRunner;
96+
tbb::concurrent_bounded_queue<int> mInputTaskQueue;
97+
std::vector<tbb::concurrent_bounded_queue<int>> mResultQueue;
98+
tbb::task_arena mTaskArena;
99+
std::atomic<bool> mStopFlag;
100+
bool mIsInitialized = false;
101+
102+
enum class GenMode {
103+
kSeq,
104+
kParallel
105+
};
106+
107+
// hybrid gen operation mode - should be either 'sequential' or 'parallel'
108+
// parallel means that we have clones of the same generator collaborating on event generation
109+
// sequential means that events will be produced in the order given by fractions; async processing is still happening
110+
GenMode mGenerationMode = GenMode::kSeq; //!
87111
};
88112

89113
} // namespace eventgen

Generators/include/Generators/GeneratorHybridParam.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ namespace eventgen
3131
struct GeneratorHybridParam : public o2::conf::ConfigurableParamHelper<GeneratorHybridParam> {
3232
std::string configFile = ""; // JSON configuration file for the generators
3333
bool randomize = false; // randomize the order of the generators, if not generator using fractions
34+
int num_workers = 1; // number of threads available for asyn/parallel event generation
3435
O2ParamDef(GeneratorHybridParam, "GeneratorHybrid");
3536
};
3637

Generators/include/Generators/GeneratorPythia8.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ class GeneratorPythia8 : public Generator
287287
// Value of -1 means unitialized; 0 will be time-dependent and values >1 <= MAX_SEED concrete reproducible seeding
288288
Pythia8GenConfig mGenConfig; // configuration object
289289

290+
static std::atomic<int> Pythia8InstanceCounter;
291+
int mThisPythia8InstanceID = 0;
292+
290293
constexpr static long MAX_SEED = 900000000;
291294

292295
ClassDefOverride(GeneratorPythia8, 1);

Generators/src/Generator.cxx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,17 @@ namespace o2
2828
namespace eventgen
2929
{
3030

31+
std::atomic<int> Generator::InstanceCounter{0};
32+
3133
/*****************************************************************/
3234
/*****************************************************************/
3335

3436
Generator::Generator() : FairGenerator("ALICEo2", "ALICEo2 Generator"),
3537
mBoost(0.)
3638
{
3739
/** default constructor **/
40+
mThisInstanceID = Generator::InstanceCounter;
41+
Generator::InstanceCounter++;
3842
}
3943

4044
/*****************************************************************/
@@ -43,6 +47,8 @@ Generator::Generator(const Char_t* name, const Char_t* title) : FairGenerator(na
4347
mBoost(0.)
4448
{
4549
/** constructor **/
50+
mThisInstanceID = Generator::InstanceCounter;
51+
Generator::InstanceCounter++;
4652
}
4753

4854
/*****************************************************************/

Generators/src/GeneratorHybrid.cxx

Lines changed: 117 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
#include <fairlogger/Logger.h>
1414
#include <algorithm>
1515

16+
#include <tbb/concurrent_queue.h>
17+
#include <tbb/task_arena.h>
18+
#include <tbb/parallel_for.h>
19+
1620
namespace o2
1721
{
1822
namespace eventgen
@@ -34,7 +38,7 @@ GeneratorHybrid::GeneratorHybrid(const std::string& inputgens)
3438
}
3539
}
3640
int index = 0;
37-
if (!mRandomize) {
41+
if (!(mRandomize || mGenerationMode == GenMode::kParallel)) {
3842
if (mFractions.size() != mInputGens.size()) {
3943
LOG(fatal) << "Number of fractions does not match the number of generators";
4044
return;
@@ -159,54 +163,116 @@ Bool_t GeneratorHybrid::Init()
159163
} else {
160164
LOG(info) << "Generators will be used in sequence, following provided fractions";
161165
}
166+
167+
if (mGenerationMode == GenMode::kParallel) {
168+
// in parallel mode we just use one queue --> collaboration
169+
mResultQueue.resize(1);
170+
} else {
171+
// in sequential mode we have one queue per generator
172+
mResultQueue.resize(gens.size());
173+
}
174+
// Create a task arena with a specified number of threads
175+
mTaskArena.initialize(GeneratorHybridParam::Instance().num_workers);
176+
177+
// the process task function actually calls event generation
178+
// when it is done it notifies the outside world by pushing it's index into an appropriate queue
179+
// This should be a lambda, which can be given at TaskPool creation time
180+
auto process_generator_task = [this](int task) {
181+
LOG(info) << "Starting eventgen for task " << task;
182+
auto& generator = gens[task];
183+
generator->clearParticles();
184+
generator->generateEvent();
185+
generator->importParticles();
186+
if (mGenerationMode == GenMode::kParallel) {
187+
mResultQueue[0].push(task);
188+
} else {
189+
mResultQueue[task].push(task);
190+
}
191+
};
192+
193+
// fundamental tbb thread-worker function
194+
auto worker_function = [this, process_generator_task]() {
195+
while (!mStopFlag) {
196+
int task;
197+
if (mInputTaskQueue.try_pop(task)) {
198+
process_generator_task(task); // Process the task
199+
} else {
200+
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Wait if no task
201+
}
202+
}
203+
std::cout << "Worker thread exiting on thread " << std::this_thread::get_id() << std::endl;
204+
};
205+
206+
// let the TBB task system run in it's own thread
207+
mTBBTaskPoolRunner = std::thread([this, worker_function]() { mTaskArena.execute([&]() { tbb::parallel_for(0, mTaskArena.max_concurrency(), [&](int) { worker_function(); }); }); });
208+
209+
mTBBTaskPoolRunner.detach();
210+
211+
// let's initially push initial generation tasks for each event generator
212+
for (size_t genindex = 0; genindex < gens.size(); ++genindex) {
213+
mInputTaskQueue.push(genindex);
214+
}
215+
mIsInitialized = true;
162216
return Generator::Init();
163217
}
164218

165-
Bool_t GeneratorHybrid::generateEvent()
219+
bool GeneratorHybrid::generateEvent()
166220
{
167-
// Order randomisation or sequence of generators
168-
// following provided fractions. If not available generators will be used sequentially
169-
if (mRandomize) {
170-
if (mRngFractions.size() != 0) {
171-
// Generate number between 0 and 1
172-
float rnum = gRandom->Rndm();
173-
// Find generator index
174-
for (int k = 0; k < mRngFractions.size(); k++) {
175-
if (rnum <= mRngFractions[k]) {
176-
mIndex = k;
177-
break;
221+
if (!mIsInitialized) {
222+
Init();
223+
}
224+
if (mGenerationMode == GenMode::kParallel) {
225+
mIndex = -1; // this means any index is welcome
226+
notifySubGenerator(0); // we shouldn't distinguish the sub-gen ids
227+
} else {
228+
// Order randomisation or sequence of generators
229+
// following provided fractions, if not generators are used in proper sequence
230+
// Order randomisation or sequence of generators
231+
// following provided fractions. If not available generators will be used sequentially
232+
if (mRandomize) {
233+
if (mRngFractions.size() != 0) {
234+
// Generate number between 0 and 1
235+
float rnum = gRandom->Rndm();
236+
// Find generator index
237+
for (int k = 0; k < mRngFractions.size(); k++) {
238+
if (rnum <= mRngFractions[k]) {
239+
mIndex = k;
240+
break;
241+
}
178242
}
243+
} else {
244+
mIndex = gRandom->Integer(mGens.size());
179245
}
180246
} else {
181-
mIndex = gRandom->Integer(mGens.size());
182-
}
183-
} else {
184-
while (mFractions[mCurrentFraction] == 0 || mseqCounter == mFractions[mCurrentFraction]) {
185-
if (mFractions[mCurrentFraction] != 0) {
186-
mseqCounter = 0;
247+
while (mFractions[mCurrentFraction] == 0 || mseqCounter == mFractions[mCurrentFraction]) {
248+
if (mFractions[mCurrentFraction] != 0) {
249+
mseqCounter = 0;
250+
}
251+
mCurrentFraction = (mCurrentFraction + 1) % mFractions.size();
187252
}
188-
mCurrentFraction = (mCurrentFraction + 1) % mFractions.size();
253+
mIndex = mCurrentFraction;
189254
}
190-
mIndex = mCurrentFraction;
255+
notifySubGenerator(mIndex);
191256
}
192-
if (mConfigs[mIndex].compare("") == 0) {
193-
LOG(info) << "GeneratorHybrid: generating event with generator " << mGens[mIndex];
194-
} else {
195-
LOG(info) << "GeneratorHybrid: generating event with generator " << mConfigs[mIndex];
196-
}
197-
gens[mIndex]->clearParticles(); // clear container of this class
198-
gens[mIndex]->generateEvent();
199-
// notify the sub event generator
200-
notifySubGenerator(mIndex);
201-
mseqCounter++;
202257
return true;
203258
}
204259

205-
Bool_t GeneratorHybrid::importParticles()
260+
bool GeneratorHybrid::importParticles()
206261
{
207-
mParticles.clear(); // clear container of mother class
208-
gens[mIndex]->importParticles();
209-
std::copy(gens[mIndex]->getParticles().begin(), gens[mIndex]->getParticles().end(), std::back_insert_iterator(mParticles));
262+
int genIndex = -1;
263+
if (mIndex == -1) {
264+
// this means parallel mode ---> we have a common queue
265+
mResultQueue[0].pop(genIndex);
266+
} else {
267+
// need to pop from a particular queue
268+
mResultQueue[mIndex].pop(genIndex);
269+
}
270+
271+
// at this moment the mIndex-th generator is ready to be used
272+
std::copy(gens[genIndex]->getParticles().begin(), gens[genIndex]->getParticles().end(), std::back_insert_iterator(mParticles));
273+
274+
// we initiate the next async event generation stage for this generator
275+
mInputTaskQueue.push(genIndex);
210276

211277
// we need to fix particles statuses --> need to enforce this on the importParticles level of individual generators
212278
for (auto& p : mParticles) {
@@ -215,6 +281,7 @@ Bool_t GeneratorHybrid::importParticles()
215281
p.SetBit(ParticleStatus::kToBeDone, true);
216282
}
217283

284+
mseqCounter++;
218285
return true;
219286
}
220287

@@ -244,6 +311,21 @@ Bool_t GeneratorHybrid::parseJSON(const std::string& path)
244311
return false;
245312
}
246313

314+
// check if there is a mode field
315+
if (doc.HasMember("mode")) {
316+
const auto& mode = doc["mode"].GetString();
317+
if (mode == "sequential") {
318+
// events are generated in the order given by fractions or random weight
319+
mGenerationMode = GenMode::kSeq;
320+
}
321+
if (mode == std::string("parallel")) {
322+
// events are generated fully in parallel and the order will be random
323+
// this is mainly for event pool generation or mono-type generators
324+
mGenerationMode = GenMode::kParallel;
325+
LOG(info) << "Setting mode to parallel";
326+
}
327+
}
328+
247329
// Put the generator names in mInputGens
248330
if (doc.HasMember("generators")) {
249331
const auto& gens = doc["generators"];

Generators/src/GeneratorPythia8.cxx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ namespace o2
4242
namespace eventgen
4343
{
4444

45+
std::atomic<int> GeneratorPythia8::Pythia8InstanceCounter;
46+
4547
/*****************************************************************/
4648
/*****************************************************************/
4749

@@ -57,6 +59,8 @@ GeneratorPythia8::GeneratorPythia8() : GeneratorPythia8(GeneratorPythia8Param::I
5759
GeneratorPythia8::GeneratorPythia8(Pythia8GenConfig const& config) : Generator("ALICEo2", "ALICEo2 Pythia8 Generator")
5860
{
5961
/** constructor **/
62+
mThisPythia8InstanceID = GeneratorPythia8::Pythia8InstanceCounter;
63+
GeneratorPythia8::Pythia8InstanceCounter++;
6064

6165
mInterface = reinterpret_cast<void*>(&mPythia);
6266
mInterfaceName = "pythia8";
@@ -116,7 +120,15 @@ void GeneratorPythia8::seedGenerator()
116120
// Otherwise will seed the generator with the state of
117121
// TRandom::GetSeed. This is the seed that is influenced from
118122
// SimConfig --seed command line options options.
119-
seed = (gRandom->TRandom::GetSeed() % (MAX_SEED + 1));
123+
seed = gRandom->TRandom::GetSeed(); // this uses the "original" seed
124+
// we advance the seed by one so that the next Pythia8 generator gets a different value
125+
if (mThisPythia8InstanceID > 0) {
126+
gRandom->Rndm();
127+
LOG(info) << "Multiple Pythia8 generator instances detected .. automatically adjusting seed further to avoid overlap ";
128+
seed = seed ^ gRandom->GetSeed(); // this uses the "current" seed
129+
}
130+
// apply max seed cuttof
131+
seed = seed % (MAX_SEED + 1);
120132
LOG(info) << "GeneratorPythia8: Using random seed from gRandom % 900000001: " << seed;
121133
}
122134
mPythia.readString("Random:setSeed on");

0 commit comments

Comments
 (0)