Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Merge branch 'actor-yield' of github.com:Takishipp/simgrid into actor-yield
[simgrid.git] / src / smpi / internals / smpi_bench.cpp
1 /* Copyright (c) 2007, 2009-2017. The SimGrid Team. All rights reserved.    */
2
3 /* This program is free software; you can redistribute it and/or modify it
4  * under the terms of the license (GNU LGPL) which comes with this package. */
5
6 #include "private.hpp"
7 #include "simgrid/modelchecker.h"
8 #include "smpi_comm.hpp"
9 #include "smpi_process.hpp"
10 #include "src/internal_config.h"
11 #include "src/mc/mc_replay.hpp"
12 #include <unordered_map>
13
14 #ifndef WIN32
15 #include <sys/mman.h>
16 #endif
17 #include <cmath>
18
19 #if HAVE_PAPI
20 #include <papi.h>
21 #endif
22
23 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(smpi_bench, smpi, "Logging specific to SMPI (benchmarking)");
24
25 double smpi_cpu_threshold = -1;
26 double smpi_host_speed;
27
28 shared_malloc_type smpi_cfg_shared_malloc = shmalloc_global;
29 double smpi_total_benched_time = 0;
30
31 extern "C" XBT_PUBLIC(void) smpi_execute_flops_(double *flops);
32 void smpi_execute_flops_(double *flops)
33 {
34   smpi_execute_flops(*flops);
35 }
36
37 extern "C" XBT_PUBLIC(void) smpi_execute_(double *duration);
38 void smpi_execute_(double *duration)
39 {
40   smpi_execute(*duration);
41 }
42
43 void smpi_execute_flops(double flops) {
44   XBT_DEBUG("Handle real computation time: %f flops", flops);
45   smx_activity_t action = simcall_execution_start("computation", flops, 1, 0);
46   simcall_set_category (action, TRACE_internal_smpi_get_category());
47   simcall_execution_wait(action);
48   smpi_switch_data_segment(smpi_process()->index());
49 }
50
51 void smpi_execute(double duration)
52 {
53   if (duration >= smpi_cpu_threshold) {
54     XBT_DEBUG("Sleep for %g to handle real computation time", duration);
55     double flops = duration * smpi_host_speed;
56     int rank = smpi_process()->index();
57     TRACE_smpi_computing_in(rank, flops);
58
59     smpi_execute_flops(flops);
60
61     TRACE_smpi_computing_out(rank);
62
63   } else {
64     XBT_DEBUG("Real computation took %g while option smpi/cpu-threshold is set to %g => ignore it", duration,
65               smpi_cpu_threshold);
66   }
67 }
68
69 void smpi_execute_benched(double duration)
70 {
71   smpi_bench_end();
72   smpi_execute(duration);
73   smpi_bench_begin();
74 }
75
76 void smpi_bench_begin()
77 {
78   if (smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP) {
79     smpi_switch_data_segment(smpi_process()->index());
80   }
81
82   if (MC_is_active() || MC_record_replay_is_active())
83     return;
84
85 #if HAVE_PAPI
86   if (not xbt_cfg_get_string("smpi/papi-events").empty()) {
87     int event_set = smpi_process()->papi_event_set();
88     // PAPI_start sets everything to 0! See man(3) PAPI_start
89     if (PAPI_LOW_LEVEL_INITED == PAPI_is_initialized()) {
90       if (PAPI_start(event_set) != PAPI_OK) {
91         // TODO This needs some proper handling.
92         XBT_CRITICAL("Could not start PAPI counters.\n");
93         xbt_die("Error.");
94       }
95     }
96   }
97 #endif
98   xbt_os_threadtimer_start(smpi_process()->timer());
99 }
100
101 void smpi_bench_end()
102 {
103   if (MC_is_active() || MC_record_replay_is_active())
104     return;
105
106   double speedup = 1;
107   xbt_os_timer_t timer = smpi_process()->timer();
108   xbt_os_threadtimer_stop(timer);
109
110 #if HAVE_PAPI
111   /**
112    * An MPI function has been called and now is the right time to update
113    * our PAPI counters for this process.
114    */
115   if (xbt_cfg_get_string("smpi/papi-events")[0] != '\0') {
116     papi_counter_t& counter_data        = smpi_process()->papi_counters();
117     int event_set                       = smpi_process()->papi_event_set();
118     std::vector<long long> event_values = std::vector<long long>(counter_data.size());
119
120     if (PAPI_stop(event_set, &event_values[0]) != PAPI_OK) { // Error
121       XBT_CRITICAL("Could not stop PAPI counters.\n");
122       xbt_die("Error.");
123     } else {
124       for (unsigned int i = 0; i < counter_data.size(); i++) {
125         counter_data[i].second += event_values[i];
126       }
127     }
128   }
129 #endif
130
131   if (smpi_process()->sampling()) {
132     XBT_CRITICAL("Cannot do recursive benchmarks.");
133     XBT_CRITICAL("Are you trying to make a call to MPI within a SMPI_SAMPLE_ block?");
134     xbt_backtrace_display_current();
135     xbt_die("Aborting.");
136   }
137
138   if (xbt_cfg_get_string("smpi/comp-adjustment-file")[0] != '\0') { // Maybe we need to artificially speed up or slow
139     // down our computation based on our statistical analysis.
140
141     smpi_trace_call_location_t* loc                            = smpi_process()->call_location();
142     std::string key                                            = loc->get_composed_key();
143     std::unordered_map<std::string, double>::const_iterator it = location2speedup.find(key);
144     if (it != location2speedup.end()) {
145       speedup = it->second;
146     }
147   }
148
149   // Simulate the benchmarked computation unless disabled via command-line argument
150   if (xbt_cfg_get_boolean("smpi/simulate-computation")) {
151     smpi_execute(xbt_os_timer_elapsed(timer)/speedup);
152   }
153
154 #if HAVE_PAPI
155   if (xbt_cfg_get_string("smpi/papi-events")[0] != '\0' && TRACE_smpi_is_enabled()) {
156     container_t container =
157         new simgrid::instr::Container(std::string("rank-") + std::to_string(smpi_process()->index()));
158     papi_counter_t& counter_data = smpi_process()->papi_counters();
159
160     for (auto const& pair : counter_data) {
161       new simgrid::instr::SetVariableEvent(
162           surf_get_clock(), container, PJ_type_get(/* countername */ pair.first.c_str(), container->type), pair.second);
163     }
164   }
165 #endif
166
167   smpi_total_benched_time += xbt_os_timer_elapsed(timer);
168 }
169
170 /* Private sleep function used by smpi_sleep() and smpi_usleep() */
171 static unsigned int private_sleep(double secs)
172 {
173   smpi_bench_end();
174
175   XBT_DEBUG("Sleep for: %lf secs", secs);
176   int rank = MPI_COMM_WORLD->rank();
177   TRACE_smpi_sleeping_in(rank, secs);
178
179   simcall_process_sleep(secs);
180
181   TRACE_smpi_sleeping_out(rank);
182
183   smpi_bench_begin();
184   return 0;
185 }
186
187 unsigned int smpi_sleep(unsigned int secs)
188 {
189   return private_sleep(static_cast<double>(secs));
190 }
191
192 int smpi_usleep(useconds_t usecs)
193 {
194   return static_cast<int>(private_sleep(static_cast<double>(usecs) / 1000000.0));
195 }
196
197 #if _POSIX_TIMERS > 0
198 int smpi_nanosleep(const struct timespec* tp, struct timespec* /*t*/)
199 {
200   return static_cast<int>(private_sleep(static_cast<double>(tp->tv_sec + tp->tv_nsec / 1000000000.0)));
201 }
202 #endif
203
204 int smpi_gettimeofday(struct timeval* tv, void* /*tz*/)
205 {
206   smpi_bench_end();
207   double now = SIMIX_get_clock();
208   if (tv) {
209     tv->tv_sec = static_cast<time_t>(now);
210 #ifdef WIN32
211     tv->tv_usec = static_cast<useconds_t>((now - tv->tv_sec) * 1e6);
212 #else
213     tv->tv_usec = static_cast<suseconds_t>((now - tv->tv_sec) * 1e6);
214 #endif
215   }
216   smpi_bench_begin();
217   return 0;
218 }
219
220 #if _POSIX_TIMERS > 0
221 int smpi_clock_gettime(clockid_t /*clk_id*/, struct timespec* tp)
222 {
223   //there is only one time in SMPI, so clk_id is ignored.
224   smpi_bench_end();
225   double now = SIMIX_get_clock();
226   if (tp) {
227     tp->tv_sec = static_cast<time_t>(now);
228     tp->tv_nsec = static_cast<long int>((now - tp->tv_sec) * 1e9);
229   }
230   smpi_bench_begin();
231   return 0;
232 }
233 #endif
234
235 extern double sg_surf_precision;
236 unsigned long long smpi_rastro_resolution ()
237 {
238   smpi_bench_end();
239   double resolution = (1/sg_surf_precision);
240   smpi_bench_begin();
241   return static_cast<unsigned long long>(resolution);
242 }
243
244 unsigned long long smpi_rastro_timestamp ()
245 {
246   smpi_bench_end();
247   double now = SIMIX_get_clock();
248
249   unsigned long long sec = static_cast<unsigned long long>(now);
250   unsigned long long pre = (now - sec) * smpi_rastro_resolution();
251   smpi_bench_begin();
252   return static_cast<unsigned long long>(sec) * smpi_rastro_resolution() + pre;
253 }
254
255 /* ****************************** Functions related to the SMPI_SAMPLE_ macros ************************************/
256 namespace {
257 class SampleLocation : public std::string {
258 public:
259   SampleLocation(bool global, const char* file, int line) : std::string(std::string(file) + ":" + std::to_string(line))
260   {
261     if (not global)
262       this->append(":" + std::to_string(smpi_process()->index()));
263   }
264 };
265
266 class LocalData {
267 public:
268   double threshold; /* maximal stderr requested (if positive) */
269   double relstderr; /* observed stderr so far */
270   double mean;      /* mean of benched times, to be used if the block is disabled */
271   double sum;       /* sum of benched times (to compute the mean and stderr) */
272   double sum_pow2;  /* sum of the square of the benched times (to compute the stderr) */
273   int iters;        /* amount of requested iterations */
274   int count;        /* amount of iterations done so far */
275   bool benching;    /* true: we are benchmarking; false: we have enough data, no bench anymore */
276
277   bool need_more_benchs() const;
278 };
279 }
280
281 std::unordered_map<SampleLocation, LocalData, std::hash<std::string>> samples;
282
283 bool LocalData::need_more_benchs() const
284 {
285   bool res = (count < iters) || (threshold > 0.0 && (count < 2 ||          // not enough data
286                                                      relstderr > threshold // stderr too high yet
287                                                      ));
288   XBT_DEBUG("%s (count:%d iter:%d stderr:%f thres:%f mean:%fs)",
289             (res ? "need more data" : "enough benchs"), count, iters, relstderr, threshold, mean);
290   return res;
291 }
292
293 void smpi_sample_1(int global, const char *file, int line, int iters, double threshold)
294 {
295   SampleLocation loc(global, file, line);
296
297   smpi_bench_end();     /* Take time from previous, unrelated computation into account */
298   smpi_process()->set_sampling(1);
299
300   auto insert = samples.emplace(loc, LocalData{
301                                          threshold, // threshold
302                                          0.0,       // relstderr
303                                          0.0,       // mean
304                                          0.0,       // sum
305                                          0.0,       // sum_pow2
306                                          iters,     // iters
307                                          0,         // count
308                                          true       // benching (if we have no data, we need at least one)
309                                      });
310   LocalData& data = insert.first->second;
311   if (insert.second) {
312     XBT_DEBUG("XXXXX First time ever on benched nest %s.", loc.c_str());
313     xbt_assert(threshold > 0 || iters > 0,
314         "You should provide either a positive amount of iterations to bench, or a positive maximal stderr (or both)");
315   } else {
316     if (data.iters != iters || data.threshold != threshold) {
317       XBT_ERROR("Asked to bench block %s with different settings %d, %f is not %d, %f. "
318                 "How did you manage to give two numbers at the same line??",
319                 loc.c_str(), data.iters, data.threshold, iters, threshold);
320       THROW_IMPOSSIBLE;
321     }
322
323     // if we already have some data, check whether sample_2 should get one more bench or whether it should emulate
324     // the computation instead
325     data.benching = data.need_more_benchs();
326     XBT_DEBUG("XXXX Re-entering the benched nest %s. %s", loc.c_str(),
327               (data.benching ? "more benching needed" : "we have enough data, skip computes"));
328   }
329 }
330
331 int smpi_sample_2(int global, const char *file, int line)
332 {
333   SampleLocation loc(global, file, line);
334   int res;
335
336   XBT_DEBUG("sample2 %s", loc.c_str());
337   auto sample = samples.find(loc);
338   if (sample == samples.end())
339     xbt_die("Y U NO use SMPI_SAMPLE_* macros? Stop messing directly with smpi_sample_* functions!");
340   LocalData& data = sample->second;
341
342   if (data.benching) {
343     // we need to run a new bench
344     XBT_DEBUG("benchmarking: count:%d iter:%d stderr:%f thres:%f; mean:%f",
345               data.count, data.iters, data.relstderr, data.threshold, data.mean);
346     res = 1;
347   } else {
348     // Enough data, no more bench (either we got enough data from previous visits to this benched nest, or we just
349     //ran one bench and need to bail out now that our job is done). Just sleep instead
350     XBT_DEBUG("No benchmark (either no need, or just ran one): count >= iter (%d >= %d) or stderr<thres (%f<=%f)."
351               " apply the %fs delay instead",
352               data.count, data.iters, data.relstderr, data.threshold, data.mean);
353     smpi_execute(data.mean);
354     smpi_process()->set_sampling(0);
355     res = 0; // prepare to capture future, unrelated computations
356   }
357   smpi_bench_begin();
358   return res;
359 }
360
361 void smpi_sample_3(int global, const char *file, int line)
362 {
363   SampleLocation loc(global, file, line);
364
365   XBT_DEBUG("sample3 %s", loc.c_str());
366   auto sample = samples.find(loc);
367   if (sample == samples.end())
368     xbt_die("Y U NO use SMPI_SAMPLE_* macros? Stop messing directly with smpi_sample_* functions!");
369   LocalData& data = sample->second;
370
371   if (not data.benching)
372     THROW_IMPOSSIBLE;
373
374   // ok, benchmarking this loop is over
375   xbt_os_threadtimer_stop(smpi_process()->timer());
376
377   // update the stats
378   data.count++;
379   double period  = xbt_os_timer_elapsed(smpi_process()->timer());
380   data.sum      += period;
381   data.sum_pow2 += period * period;
382   double n       = static_cast<double>(data.count);
383   data.mean      = data.sum / n;
384   data.relstderr = sqrt((data.sum_pow2 / n - data.mean * data.mean) / n) / data.mean;
385   if (data.need_more_benchs()) {
386     data.mean = period; // Still in benching process; We want sample_2 to simulate the exact time of this loop
387     // occurrence before leaving, not the mean over the history
388   }
389   XBT_DEBUG("Average mean after %d steps is %f, relative standard error is %f (sample was %f)",
390             data.count, data.mean, data.relstderr, period);
391
392   // That's enough for now, prevent sample_2 to run the same code over and over
393   data.benching = false;
394 }
395
396 extern "C" { /** These functions will be called from the user code **/
397 smpi_trace_call_location_t* smpi_trace_get_call_location()
398 {
399   return smpi_process()->call_location();
400 }
401
402 void smpi_trace_set_call_location(const char* file, const int line)
403 {
404   smpi_trace_call_location_t* loc = smpi_process()->call_location();
405
406   loc->previous_filename   = loc->filename;
407   loc->previous_linenumber = loc->linenumber;
408   loc->filename            = file;
409   loc->linenumber          = line;
410 }
411
412 /** Required for Fortran bindings */
413 void smpi_trace_set_call_location_(const char* file, int* line)
414 {
415   smpi_trace_set_call_location(file, *line);
416 }
417
418 /** Required for Fortran if -fsecond-underscore is activated */
419 void smpi_trace_set_call_location__(const char* file, int* line)
420 {
421   smpi_trace_set_call_location(file, *line);
422 }
423 }
424
425 void smpi_bench_destroy()
426 {
427   samples.clear();
428 }