The Gaudi Framework  v33r0 (d5ea422b)
CPUCruncher.cpp
Go to the documentation of this file.
1 /***********************************************************************************\
2 * (c) Copyright 1998-2019 CERN for the benefit of the LHCb and ATLAS collaborations *
3 * *
4 * This software is distributed under the terms of the Apache version 2 licence, *
5 * copied verbatim in the file "LICENSE". *
6 * *
7 * In applying this licence, CERN does not waive the privileges and immunities *
8 * granted to it by virtue of its status as an Intergovernmental Organization *
9 * or submit itself to any jurisdiction. *
10 \***********************************************************************************/
11 #include "CPUCruncher.h"
13 #include "HiveNumbers.h"
14 #include <ctime>
15 #include <sys/resource.h>
16 #include <sys/times.h>
17 
18 #include <tbb/tick_count.h>
19 #include <thread>
20 
22 
24 
25 #define ON_DEBUG if ( msgLevel( MSG::DEBUG ) )
26 #define DEBUG_MSG ON_DEBUG debug()
27 
28 #define ON_VERBOSE if ( msgLevel( MSG::VERBOSE ) )
29 #define VERBOSE_MSG ON_VERBOSE verbose()
30 
31 //------------------------------------------------------------------------------
32 
33 CPUCruncher::CPUCruncher( const std::string& name, // the algorithm instance name
34  ISvcLocator* pSvc )
35  : GaudiAlgorithm( name, pSvc ) {
36 
37  // Register the algo in the static concurrent hash map in order to
38  // monitor the # of copies
39  CHM::accessor name_ninstances;
40  m_name_ncopies_map.insert( name_ninstances, name );
41  name_ninstances->second += 1;
42 }
43 
45  for ( uint i = 0; i < m_inputHandles.size(); ++i ) delete m_inputHandles[i];
46 
47  for ( uint i = 0; i < m_outputHandles.size(); ++i ) delete m_outputHandles[i];
48 }
49 
51  auto sc = GaudiAlgorithm::initialize();
52  if ( !sc ) return sc;
53 
54  m_crunchSvc = serviceLocator()->service( "CPUCrunchSvc" );
55  if ( !m_crunchSvc.isValid() ) {
56  fatal() << "unable to acquire CPUCruncSvc" << endmsg;
57  return StatusCode::FAILURE;
58  }
59 
60  // if an algorithm was setup to sleep, for whatever period, it effectively becomes I/O-bound
61  if ( m_sleepFraction != 0.0f ) setIOBound( true );
62 
63  // This is a bit ugly. There is no way to declare a vector of DataObjectHandles, so
64  // we need to wait until initialize when we've read in the input and output key
65  // properties, and know their size, and then turn them
66  // into Handles and register them with the framework by calling declareProperty. We
67  // could call declareInput/declareOutput on them too.
68 
69  int i = 0;
70  for ( auto k : m_inpKeys ) {
71  DEBUG_MSG << "adding input key " << k << endmsg;
73  declareProperty( "dummy_in_" + std::to_string( i ), *( m_inputHandles.back() ) );
74  i++;
75  }
76 
77  i = 0;
78  for ( auto k : m_outKeys ) {
79  DEBUG_MSG << "adding output key " << k << endmsg;
81  declareProperty( "dummy_out_" + std::to_string( i ), *( m_outputHandles.back() ) );
82  i++;
83  }
84 
85  return sc;
86 }
87 
88 //------------------------------------------------------------------------------
90  //
91  for ( const auto& k : outputDataObjs() ) {
92  auto outputHandle = new DataObjectHandle<DataObject>( k, Gaudi::DataHandle::Writer, this );
93  VERBOSE_MSG << "found late-attributed output: " << outputHandle->objKey() << endmsg;
94  m_outputHandles.push_back( outputHandle );
95  declareProperty( "dummy_out_" + outputHandle->objKey(), *( m_outputHandles.back() ) );
96  }
97 
98  initDataHandleHolder();
99 
100  m_declAugmented = true;
101 }
102 
103 //------------------------------------------------------------------------------
104 
105 StatusCode CPUCruncher::execute() // the execution of the algorithm
106 {
107 
109 
110  float crunchtime;
111 
112  if ( m_local_rndm_gen ) {
113  /* This will disappear with a thread safe random number generator service.
114  * Use basic Box-Muller to generate Gaussian random numbers.
115  * The quality is not good for in depth study given that the generator is a
116  * linear congruent.
117  * Throw away basically a free number: we are in a cpu cruncher after all.
118  * The seed is taken from the clock, but we could assign a seed per module to
119  * ensure reproducibility.
120  *
121  * This is not an overkill but rather an exercise towards a thread safe
122  * random number generation.
123  */
124 
125  auto getGausRandom = []( double mean, double sigma ) -> double {
126  unsigned int seed = std::clock();
127 
128  auto getUnifRandom = []( unsigned int& seed ) -> double {
129  // from "Numerical Recipes"
130  constexpr unsigned int m = 232;
131  constexpr unsigned int a = 1664525;
132  constexpr unsigned int c = 1013904223;
133  seed = ( a * seed + c ) % m;
134  const double unif = double( seed ) / m;
135  return unif;
136  };
137 
138  double unif1, unif2;
139  do {
140  unif1 = getUnifRandom( seed );
141  unif2 = getUnifRandom( seed );
142  } while ( unif1 == 0. );
143 
144  const double normal = sqrt( -2. * log( unif1 ) ) * cos( 2 * M_PI * unif2 );
145 
146  return normal * sigma + mean;
147  };
148 
149  crunchtime = fabs( getGausRandom( m_avg_runtime * ( 1. - m_sleepFraction ), m_var_runtime ) );
150  // End Of temp block
151  } else {
152  // Should be a member.
153  HiveRndm::HiveNumbers rndmgaus( randSvc(), Rndm::Gauss( m_avg_runtime * ( 1. - m_sleepFraction ), m_var_runtime ) );
154  crunchtime = std::fabs( rndmgaus() );
155  }
156  unsigned int crunchtime_ms = 1000 * crunchtime;
157 
158  // Prepare to sleep (even if we won't enter the following if clause for sleeping).
159  // This is needed to distribute evenly among all algorithms the overhead (around sleeping) which is harmful when
160  // trying to achieve uniform distribution of algorithm timings.
161  const double dreamtime = m_avg_runtime * m_sleepFraction;
162  const std::chrono::duration<double> dreamtime_duration( dreamtime );
163  tbb::tick_count startSleeptbb;
164  tbb::tick_count endSleeptbb;
165 
166  // Start to measure the total time here, together with the dreaming process straight ahead
167  tbb::tick_count starttbb = tbb::tick_count::now();
168 
169  // If the algorithm was set as I/O-bound, we will replace requested part of crunching with plain sleeping
170  if ( isIOBound() ) {
171  // in this block (and not in other places around) msgLevel is checked for the same reason as above, when
172  // preparing to sleep several lines above: to reduce as much as possible the overhead around sleeping
173  DEBUG_MSG << "Dreaming time will be: " << int( 1000 * dreamtime ) << " ms" << endmsg;
174 
175  ON_DEBUG startSleeptbb = tbb::tick_count::now();
176  std::this_thread::sleep_for( dreamtime_duration );
177  ON_DEBUG endSleeptbb = tbb::tick_count::now();
178 
179  // actual sleeping time can be longer due to scheduling or resource contention delays
180  ON_DEBUG {
181  const double actualDreamTime = ( endSleeptbb - startSleeptbb ).seconds();
182  debug() << "Actual dreaming time was: " << int( 1000 * actualDreamTime ) << "ms" << endmsg;
183  }
184  } // end of "sleeping block"
185 
186  DEBUG_MSG << "Crunching time will be: " << crunchtime_ms << " ms" << endmsg;
188  DEBUG_MSG << "Start event " << context.evt() << " in slot " << context.slot() << " on pthreadID " << std::hex
189  << pthread_self() << std::dec << endmsg;
190 
191  VERBOSE_MSG << "inputs number: " << m_inputHandles.size() << endmsg;
192  for ( auto& inputHandle : m_inputHandles ) {
193  if ( !inputHandle->isValid() ) continue;
194 
195  VERBOSE_MSG << "get from TS: " << inputHandle->objKey() << endmsg;
196  DataObject* obj = nullptr;
197  for ( unsigned int i = 0; i < m_rwRepetitions; ++i ) { obj = inputHandle->get(); }
198  if ( obj == nullptr ) error() << "A read object was a null pointer." << endmsg;
199  }
200 
201  m_crunchSvc->crunch_for( std::chrono::milliseconds( crunchtime_ms ) );
202 
203  // Return error on fraction of events if configured
204  if ( m_failNEvents > 0 && context.evt() > 0 && ( context.evt() % m_failNEvents ) == 0 ) {
205  return StatusCode::FAILURE;
206  }
207 
208  VERBOSE_MSG << "outputs number: " << m_outputHandles.size() << endmsg;
209  for ( auto& outputHandle : m_outputHandles ) {
210  if ( !outputHandle->isValid() ) continue;
211 
212  VERBOSE_MSG << "put to TS: " << outputHandle->objKey() << endmsg;
213  outputHandle->put( new DataObject() );
214  }
215 
216  tbb::tick_count endtbb = tbb::tick_count::now();
217  const double actualRuntime = ( endtbb - starttbb ).seconds();
218 
219  DEBUG_MSG << "Finish event " << context.evt() << " in " << int( 1000 * actualRuntime ) << " ms" << endmsg;
220 
221  DEBUG_MSG << "Timing: ExpectedCrunchtime= " << crunchtime_ms << " ms. ExpectedDreamtime= " << int( 1000 * dreamtime )
222  << " ms. ActualTotalRuntime= " << int( 1000 * actualRuntime )
223  << " ms. Ratio= " << ( crunchtime + dreamtime ) / actualRuntime << endmsg;
224 
225  setFilterPassed( !m_invertCFD );
226 
227  return StatusCode::SUCCESS;
228 }
229 
230 //------------------------------------------------------------------------------
231 
232 StatusCode CPUCruncher::finalize() // the finalization of the algorithm
233 {
234  MsgStream log( msgSvc(), name() );
235 
236  unsigned int ninstances;
237 
238  {
239  CHM::const_accessor const_name_ninstances;
240  m_name_ncopies_map.find( const_name_ninstances, name() );
241  ninstances = const_name_ninstances->second;
242  }
243 
244  constexpr double s2ms = 1000.;
245  // do not show repetitions
246  if ( ninstances != 0 ) {
247  info() << "Summary: name= " << name() << "\t avg_runtime= " << m_avg_runtime * s2ms << "\t n_clones= " << ninstances
248  << endmsg;
249 
250  CHM::accessor name_ninstances;
251  m_name_ncopies_map.find( name_ninstances, name() );
252  name_ninstances->second = 0;
253  }
254 
255  return GaudiAlgorithm::finalize();
256 }
257 
258 //------------------------------------------------------------------------------
StatusCode execute() override
the execution of the algorithm
SmartIF< ICPUCrunchSvc > m_crunchSvc
Definition: CPUCruncher.h:88
Definition of the MsgStream class used to transmit messages.
Definition: MsgStream.h:34
virtual std::chrono::milliseconds crunch_for(const std::chrono::milliseconds &) const =0
Gaudi::Property< float > m_sleepFraction
Definition: CPUCruncher.h:72
The ISvcLocator is the interface implemented by the Service Factory in the Application Manager to loc...
Definition: ISvcLocator.h:35
A class that implements a search for prime numbers.
Definition: CPUCruncher.h:29
Gaudi::Property< bool > m_loader
Definition: CPUCruncher.h:63
StatusCode initialize() override
standard initialization method
auto sqrt(std::chrono::duration< Rep, Period > d)
sqrt for std::chrono::duration
Definition: Counters.h:34
virtual ~CPUCruncher()
virtual & protected desctrustor
Definition: CPUCruncher.cpp:44
T to_string(T... args)
T clock(T... args)
bool isValid() const
Allow for check if smart pointer is valid.
Definition: SmartIF.h:72
Gaudi::Property< unsigned int > m_rwRepetitions
Definition: CPUCruncher.h:71
constexpr static const auto SUCCESS
Definition: StatusCode.h:96
T sleep_for(T... args)
Parameters for the Gauss random number generation.
std::vector< DataObjectHandle< DataObject > * > m_outputHandles
Definition: CPUCruncher.h:83
This class represents an entry point to all the event specific data.
Definition: EventContext.h:34
#define VERBOSE_MSG
Definition: CPUCruncher.cpp:29
STL class.
#define DECLARE_COMPONENT(type)
tbb::concurrent_hash_map< std::string, unsigned int > CHM
Definition: CPUCruncher.h:32
T push_back(T... args)
static CHM m_name_ncopies_map
Definition: CPUCruncher.h:85
This class is used for returning status codes from appropriate routines.
Definition: StatusCode.h:61
constexpr double m
StatusCode finalize() override
standard finalization method
Gaudi::Property< bool > m_invertCFD
Definition: CPUCruncher.h:75
The useful base class for data processing algorithms.
std::vector< DataObjectHandle< DataObject > * > m_inputHandles
Definition: CPUCruncher.h:82
GAUDI_API const EventContext & currentContext()
const std::string & context() const
Returns the "context" string. Used to identify different processing states.
Definition: GaudiCommon.h:717
T fabs(T... args)
#define DEBUG_MSG
Definition: CPUCruncher.cpp:26
T size(T... args)
void declareRuntimeRequestedOutputs()
The CPU intensive function.
Definition: CPUCruncher.cpp:89
Gaudi::Property< double > m_avg_runtime
Definition: CPUCruncher.h:68
StatusCode initialize() override
Its initialization.
Definition: CPUCruncher.cpp:50
T back(T... args)
CPUCruncher()
the default constructor is disabled
constexpr static const auto FAILURE
Definition: StatusCode.h:97
Gaudi::Property< unsigned int > m_failNEvents
Definition: CPUCruncher.h:76
T hex(T... args)
Gaudi::Property< std::vector< std::string > > m_outKeys
Definition: CPUCruncher.h:66
Gaudi::Property< std::vector< std::string > > m_inpKeys
Definition: CPUCruncher.h:65
A DataObject is the base class of any identifiable object on any data store.
Definition: DataObject.h:40
StatusCode finalize() override
the finalization of the algorithm
MsgStream & endmsg(MsgStream &s)
MsgStream Modifier: endmsg. Calls the output method of the MsgStream.
Definition: MsgStream.h:202
Gaudi::Property< bool > m_local_rndm_gen
Definition: CPUCruncher.h:70
Gaudi::Property< double > m_var_runtime
Definition: CPUCruncher.h:69
#define ON_DEBUG
Definition: CPUCruncher.cpp:25
bool m_declAugmented
Definition: CPUCruncher.h:62