Belle II Software development
FullSimTimingModule.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include <simulation/modules/fullsim/FullSimTimingModule.h>
10#include <simulation/kernel/RunManager.h>
11#include <framework/gearbox/Unit.h>
12#include <G4EventManager.hh>
13#include <G4VSteppingVerbose.hh>
14#include <G4RegionStore.hh>
15#include <framework/logging/Logger.h>
16
17#include <TProfile.h>
18#include <TFile.h>
19
20using namespace Belle2;
21
22//-----------------------------------------------------------------
23// Register the Module
24//-----------------------------------------------------------------
25REG_MODULE(FullSimTiming);
26
27//-----------------------------------------------------------------
28// Implementation
29//-----------------------------------------------------------------
30
32{
33 // Set module properties
34 setDescription("Provide more detailed timing info for FullSim module");
35
36 //So, we have to get rid of the old instance first if we want to get our
37 //timing instance in there, otherwise we get an G4Exception
38 G4VSteppingVerbose* old = G4VSteppingVerbose::GetInstance();
39 if (old) {
40 delete old;
41 G4VSteppingVerbose::SetInstance(nullptr);
42 }
43 //Now we create our timing class and set it as the class to be used. Set the callback to our processStep member
44 m_timing = new Simulation::StepTiming([&](G4Track * track, G4LogicalVolume * volume, double time) {
45 processStep(track, volume, time);
46 });
47 G4VSteppingVerbose::SetInstance(m_timing);
48
49 // Parameter definitions
50 addParam("rootFile", m_rootFileName, "Name of the output root file. If none "
51 " is given, no file will be created", std::string(""));
52}
53
54
56{
57 m_isInitialized = false;
58
59 //There are two (useful) scenarios: either this module is in front or after
60 //the FullSim module. In both cases we want it to work correctly. As the
61 //FullSim module overrides the verbosity of the SteppingManager we have to be
62 //careful when to initialize, it must be after the FullSim::initialize(). So
63 //either here if we are after the FullSim module or in the first event() call
64 //if we are before
65
67 //So, we check if the Simulation has been intialized already. If so we can
68 //initialize the StepTiming now, otherwise we do it in the first call to the
69 //event function. For this we just check if the PrimaryGeneratorAction has
70 //been set
71 if (runManager.GetUserPrimaryGeneratorAction()) {
72 //initialization is done by just calling the event function once
73 event();
74 }
75}
76
78{
79 if (!m_isInitialized) {
80 //Override verbosity of the Stepping manager to make sure that the
81 //TrackingVerbose instance is called
82 G4SteppingManager* stepping = G4EventManager::GetEventManager()->GetTrackingManager()->GetSteppingManager();
83 //Set the timing class
84 stepping->SetVerbose(m_timing);
85 //Make sure the SteppingManager is set to verbose otherwise the timing class will not be called
86 stepping->SetVerboseLevel(1);
87 //Make sure the timing class knows about the stepping manager
88 m_timing->SetManager(stepping);
89 //And set it to silent so the TrackingManager does not print the TrackBanner
90 m_timing->SetSilent(1);
91 m_timing->SetSilentStepInfo(1);
92 //Ok, we want to plot the regions in the order they were created so we need
93 //to map from region to index in the store
94 int index {0};
95 for (G4Region* region : * (G4RegionStore::GetInstance())) {
96 //We don't care about regions wihtout volumes
97 if (region->GetNumberOfRootVolumes() == 0) continue;
98 //Increase the index otherwise
99 m_regionIndices[region] = index++;
100 }
101 //create profile to store timings
102 m_timingProfile = new TProfile(
103 "SimulationTiming", "Simulation Timing;region;stepping time per event / ms",
104 index, 0, index, "s"
105 );
106 //Set counter variables
107 m_totalTime = 0;
108 m_eventCount = 0;
109 m_eventTime = 0;
110 //And we only do this once
111 m_isInitialized = true;
112 }
113 //If the eventTime is >0 we recorded at least one step, so let's update the
114 //profile and reset the counters
115 if (m_eventTime > 0) {
116 ++m_eventCount;
117 m_eventTime = 0;
118 for (auto& it : m_regionCache) {
119 int index = m_regionIndices[it.first];
120 m_timingProfile->Fill(index, it.second);
121 it.second = 0;
122 }
123 }
124}
125
126void FullSimTimingModule::processStep(G4Track*, G4LogicalVolume* volume, double time)
127{
128 const G4Region* const region = volume->GetRegion();
129 //Check if we are still in the same region to speed up the lookup
130 if (m_lastRegion == end(m_regionCache) || m_lastRegion->first != region) {
131 //Apparently not, so find or create the region in the Cache
132 m_lastRegion = m_regionCache.insert(std::make_pair(region, 0.0)).first;
133 }
134 //Record times
135 m_lastRegion->second += time;
136 m_totalTime += time;
137 m_eventTime += time;
138}
139
141{
142 //Call event one last time to get the last timing in case we were before the FullSim module
143 event();
144 //Ok, now finish the results
145 B2RESULT("FullSimTiming: Total simulation stepping time: " << (m_totalTime / Unit::s) << " s");
146 B2RESULT("FullSimTiming: Number of simulated events: " << m_eventCount);
147 //Remove stat box
148 m_timingProfile->SetStats(0);
149 //Convert to ms
150 m_timingProfile->Scale(1. / Unit::ms);
151 //Set the labels of the profile to the region names and check the maximum length
152 size_t regionWidth {6};
153 for (auto& it : m_regionIndices) {
154 m_timingProfile->GetXaxis()->SetBinLabel(it.second + 1, it.first->GetName().c_str());
155 regionWidth = std::max(regionWidth, it.first->GetName().size());
156 }
157 //Do we want a root file?
158 if (!m_rootFileName.empty()) {
159 TFile* file = new TFile(m_rootFileName.c_str(), "RECREATE");
160 file->cd();
161 m_timingProfile->Write();
162 file->Close();
163 }
164 if (m_totalTime > 0) {
165 //Now print the result as a table. But first we need the total average
166 double totalAvg {0};
167 size_t maxContentWidth {4};
168 size_t maxErrorWidth {4};
169 for (int i = 0; i < m_timingProfile->GetNbinsX(); ++i) {
170 //We want to calculate the total avg per event and the width required to
171 //show all times and their errors. So we need times and errors in ms
172 const double content = m_timingProfile->GetBinContent(i + 1);
173 const double error = m_timingProfile->GetBinError(i + 1);
174 //add up the averages.
175 totalAvg += content;
176 //and calculate the number of digits
177 const size_t contentWidth = std::floor(std::log10(content > 0 ? content : 1) + 1);
178 const size_t errorWidth = std::floor(std::log10(error > 0 ? error : 1) + 1);
179 //and get the maximum, including dot and 2 digits
180 maxContentWidth = std::max(maxContentWidth, contentWidth + 3);
181 maxErrorWidth = std::max(maxErrorWidth, errorWidth + 3);
182 }
183 //We want percentages ...
184 totalAvg /= 100;
185 //And finally we print the table
186 B2RESULT("FullSimTiming: Simulation stepping time per event and region");
187 for (int i = 0; i < m_timingProfile->GetNbinsX(); ++i) {
188 B2RESULT(" "
189 << std::setw(regionWidth + 1) << std::left << m_timingProfile->GetXaxis()->GetBinLabel(i + 1) << " "
190 << std::right << std::fixed << std::setprecision(2)
191 << std::setw(maxContentWidth) << m_timingProfile->GetBinContent(i + 1) << " ms +- "
192 << std::setw(maxErrorWidth) << m_timingProfile->GetBinError(i + 1) << " ms ("
193 << std::setw(6) << (m_timingProfile->GetBinContent(i + 1) / totalAvg) << " %)"
194 );
195 }
196 }
197}
std::string m_rootFileName
filename of the root file to store the timing profile
double m_totalTime
total time spent stepping through the simulation geometry
bool m_isInitialized
remember whether we aleady initialized everything
std::map< constG4Region *, double >::iterator m_lastRegion
iterator pointing to the region of the last step to speed up lookup
Simulation::StepTiming * m_timing
instance of the StepTiming class which records the step times in geant4
virtual void initialize() override
Setup the timing structure.
virtual void event() override
Save the simulation time for each region in a profile.
FullSimTimingModule()
Constructor: Sets the description, the properties and the parameters of the module.
virtual void terminate() override
Save the output.
double m_eventTime
stepping time of the current event
void processStep(G4Track *track, G4LogicalVolume *volume, double time)
store the time it took a Geant4 track for one step inside a volume
unsigned int m_eventCount
number of events with m_eventTime>0
std::map< const G4Region *, int > m_regionIndices
map between region and and bin indices in the TProfile
std::map< const G4Region *, double > m_regionCache
map containing the amount of time spent in each region
TProfile * m_timingProfile
profile of the time spent per event in each region
Base class for Modules.
Definition: Module.h:72
void setDescription(const std::string &description)
Sets the description of the module.
Definition: Module.cc:214
The run manager controls the flow of the Geant4 program and manages the event loop(s) within a run.
Definition: RunManager.h:32
static RunManager & Instance()
Static method to get a reference to the RunManager instance.
Definition: RunManager.cc:29
Class to perform more detailed timing studies of the Geant4 simulation.
Definition: StepTiming.h:34
static const double ms
[millisecond]
Definition: Unit.h:96
static const double s
[second]
Definition: Unit.h:95
void addParam(const std::string &name, T &paramVariable, const std::string &description, const T &defaultValue)
Adds a new parameter to the module.
Definition: Module.h:560
#define REG_MODULE(moduleName)
Register the given module (without 'Module' suffix) with the framework.
Definition: Module.h:650
Abstract base class for different kinds of events.