The Gaudi Framework  master (181af51f)
Loading...
Searching...
No Matches
CPUCrunchSvc.cpp
Go to the documentation of this file.
1/***********************************************************************************\
2* (c) Copyright 1998-2024 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 "CPUCrunchSvc.h"
12
14
15#include <sys/times.h>
16#include <tbb/tick_count.h>
17
19
20#define ON_DEBUG if ( msgLevel( MSG::DEBUG ) )
21#define DEBUG_MSG ON_DEBUG debug()
22
23#define ON_VERBOSE if ( msgLevel( MSG::VERBOSE ) )
24#define VERBOSE_MSG ON_VERBOSE verbose()
25
26namespace {
27 // idea coming from The art of computer programming by Knuth
28 constexpr bool essentiallyEqual( double const a, double const b ) {
29 return std::abs( a - b ) <= std::min( std::abs( a ), std::abs( b ) ) * std::numeric_limits<double>::epsilon();
30 }
31} // namespace
32
33/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
34
35CPUCrunchSvc::CPUCrunchSvc( const std::string& name, ISvcLocator* svc ) : base_class( name, svc ) {}
36
37/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
38
40 debug() << "initialize" << endmsg;
41
42 if ( base_class::initialize().isFailure() ) {
43 error() << "Error initializing base class" << endmsg;
45 }
46
47 calibrate();
48
50}
51
52/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
53
54/*
55Calibrate the crunching finding the right relation between max number to be searched and time spent.
56The relation is a sqrt for times greater than 10^-4 seconds.
57*/
59 if ( m_niters_vect.value().size() == 0 ) {
60 m_niters_vect = { 0, 500, 600, 700, 800, 1000, 1300, 1600, 2000, 2300,
61 2600, 3000, 3300, 3500, 3900, 4200, 5000, 6000, 8000, 10000,
62 12000, 15000, 17000, 20000, 25000, 30000, 35000, 40000, 50000, 60000 };
63 if ( !m_shortCalib ) {
64 m_niters_vect.value().push_back( 80000 );
65 m_niters_vect.value().push_back( 100000 );
66 m_niters_vect.value().push_back( 150000 );
67 m_niters_vect.value().push_back( 200000 );
68 }
69 }
70
71 if ( m_niters_vect.value().at( 0 ) != 0 ) {
72 warning() << "NIterationsVect[0]= " << m_niters_vect.value().at( 0 ) << " but needs to be zero. resetting it."
73 << endmsg;
74 m_niters_vect.value().at( 0 ) = 0;
75 }
76
77 m_times_vect.resize( m_niters_vect.value().size() );
78 m_times_vect.at( 0 ) = 0;
79
80 const unsigned int minCalibTime_us = m_minCalibTime * 1000;
81
82 // warm it up by doing 20k iterations
83 findPrimes( 20000 );
84
85 for ( int irun = 0; irun < m_numCalibRuns; ++irun ) {
86
87 debug() << "Starting calibration run " << irun + 1 << " ..." << endmsg;
88 for ( unsigned int i = 1; i < m_niters_vect.value().size(); ++i ) {
89 unsigned int niters = m_niters_vect.value().at( i );
90 unsigned int trials = 30;
91 do {
92 auto start_cali = tbb::tick_count::now();
93 findPrimes( niters );
94 auto stop_cali = tbb::tick_count::now();
95 auto deltat = ( stop_cali - start_cali ).seconds();
96 m_times_vect.at( i ) = deltat * 1000000; // in microseconds
97 debug() << " Calibration: # iters = " << niters << " => " << m_times_vect.at( i ) << " us" << endmsg;
98 trials--;
99 } while ( trials > 0 && m_times_vect.at( i ) < m_times_vect.at( i - 1 ) ); // make sure that they are monotonic
100
101 if ( i == m_niters_vect.value().size() - 1 && minCalibTime_us != 0 ) {
102 if ( m_times_vect.at( i ) < minCalibTime_us ) {
103 debug() << " increasing calib vect with " << int( m_niters_vect.value().back() * 1.2 )
104 << " iterations to reach min calib time of " << m_minCalibTime.value() << " ms " << endmsg;
105 m_niters_vect.value().push_back( int( m_niters_vect.value().back() * 1.2 ) );
106 m_times_vect.push_back( 0. );
107 }
108 }
109 }
110 }
111 if ( essentiallyEqual( m_corrFact, 1. ) ) {
112 debug() << "Adjusting times with correction factor " << m_corrFact.value() << endmsg;
113 for ( auto& t : m_times_vect ) { t = t * m_corrFact; }
114 }
115 debug() << "Calibration finished!" << endmsg;
116}
117
118/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
119
120unsigned int CPUCrunchSvc::getNCaliIters( std::chrono::microseconds runtime ) const {
121
122 unsigned int smaller_i = 0;
123 double time = 0.;
124 bool found = false;
125 double corrRuntime = runtime.count(); // * m_corrFact;
126 // We know that the first entry is 0, so we start to iterate from 1
127 for ( unsigned int i = 1; i < m_times_vect.size(); i++ ) {
128 time = m_times_vect.at( i );
129 if ( time > corrRuntime ) {
130 smaller_i = i - 1;
131 found = true;
132 break;
133 }
134 }
135
136 // Case 1: we are outside the interpolation range, we take the last 2 points
137 if ( not found ) smaller_i = m_times_vect.size() - 2;
138
139 // Case 2: we maeke a linear interpolation
140 // y=mx+q
141 const auto x0 = m_times_vect.at( smaller_i );
142 const auto x1 = m_times_vect.at( smaller_i + 1 );
143 const auto y0 = m_niters_vect.value().at( smaller_i );
144 const auto y1 = m_niters_vect.value().at( smaller_i + 1 );
145 const double m = (double)( y1 - y0 ) / (double)( x1 - x0 );
146 const double q = y0 - m * x0;
147
148 const unsigned int nCaliIters = m * corrRuntime + q;
149
150 VERBOSE_MSG << "x0: " << x0 << " x1: " << x1 << " y0: " << y0 << " y1: " << y1 << " m: " << m << " q: " << q
151 << " itr: " << nCaliIters << endmsg;
152
153 return nCaliIters;
154}
155
156/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
157
158void CPUCrunchSvc::findPrimes( const unsigned int n_iterations ) const {
159 // Flag to trigger the allocation
160 bool is_prime;
161
162 // Let's prepare the material for the allocations
163 unsigned int primes_size = 1;
164 unsigned long* primes = new unsigned long[primes_size];
165 primes[0] = 2;
166
167 unsigned long i = 2;
168
169 // Loop on numbers
170 for ( unsigned long int iiter = 0; iiter < n_iterations; iiter++ ) {
171 // Once at max, it returns to 0
172 i += 1;
173
174 // Check if it can be divided by the smaller ones
175 is_prime = true;
176 for ( unsigned long j = 2; j < i && is_prime; ++j ) {
177 if ( i % j == 0 ) is_prime = false;
178 } // end loop on numbers < than tested one
179
180 if ( is_prime ) {
181 // copy the array of primes (INEFFICIENT ON PURPOSE!)
182 unsigned int new_primes_size = 1 + primes_size;
183 unsigned long* new_primes = new unsigned long[new_primes_size];
184
185 for ( unsigned int prime_index = 0; prime_index < primes_size; prime_index++ ) {
186 new_primes[prime_index] = primes[prime_index];
187 }
188 // attach the last prime
189 new_primes[primes_size] = i;
190
191 // Update primes array
192 delete[] primes;
193 primes = new_primes;
194 primes_size = new_primes_size;
195 } // end is prime
196
197 } // end of while loop
198
199 // Fool Compiler optimisations:
200 for ( unsigned int prime_index = 0; prime_index < primes_size; prime_index++ )
201 if ( primes[prime_index] == 4 )
202 debug() << "This does never happen, but it's necessary too fool aggressive compiler optimisations!" << endmsg;
203
204 delete[] primes;
205}
206
207/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
208
209// Translate the required crunch time into interations, and do it
210std::chrono::milliseconds CPUCrunchSvc::crunch_for( const std::chrono::milliseconds& crunchtime ) const {
211
212 const unsigned int niters = getNCaliIters( crunchtime );
213
214 auto start_cali = tbb::tick_count::now();
215 findPrimes( niters );
216 auto stop_cali = tbb::tick_count::now();
217
218 std::chrono::milliseconds actual( int( 1000 * ( stop_cali - start_cali ).seconds() ) );
219
220 DEBUG_MSG << "crunch for " << crunchtime.count() << " ms == " << niters << " iter. actual time: " << actual.count()
221 << " ms. ratio: " << float( actual.count() ) / crunchtime.count() << endmsg;
222
223 return actual;
224}
#define DEBUG_MSG
#define VERBOSE_MSG
MsgStream & endmsg(MsgStream &s)
MsgStream Modifier: endmsg. Calls the output method of the MsgStream.
Definition MsgStream.h:198
#define DECLARE_COMPONENT(type)
std::vector< unsigned int > m_times_vect
virtual StatusCode initialize() override
Gaudi::Property< int > m_numCalibRuns
Gaudi::Property< bool > m_shortCalib
CPUCrunchSvc(const std::string &name, ISvcLocator *svc)
Gaudi::Property< std::vector< unsigned int > > m_niters_vect
void findPrimes(unsigned int) const
Gaudi::Property< float > m_corrFact
Gaudi::Property< unsigned int > m_minCalibTime
std::chrono::milliseconds crunch_for(const std::chrono::milliseconds &crunchtime) const override
unsigned int getNCaliIters(std::chrono::microseconds runtime) const
MsgStream & error() const
shortcut for the method msgStream(MSG::ERROR)
MsgStream & warning() const
shortcut for the method msgStream(MSG::WARNING)
MsgStream & debug() const
shortcut for the method msgStream(MSG::DEBUG)
The ISvcLocator is the interface implemented by the Service Factory in the Application Manager to loc...
Definition ISvcLocator.h:42
const std::string & name() const override
Retrieve name of the service.
Definition Service.cpp:333
This class is used for returning status codes from appropriate routines.
Definition StatusCode.h:64
constexpr static const auto SUCCESS
Definition StatusCode.h:99
constexpr static const auto FAILURE
Definition StatusCode.h:100
Gaudi::ParticleID abs(const Gaudi::ParticleID &p)
Return the absolute value for a PID.
Definition ParticleID.h:191