899c7ea7091a8eb43f99af15957c0f792e5c0328
[alexxy/gromacs.git] / src / gromacs / timing / wallcycle.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5  * Copyright (c) 2001-2008, The GROMACS development team.
6  * Copyright (c) 2013,2014,2015,2016,2017 by the GROMACS development team.
7  * Copyright (c) 2018,2019,2020,2021, by the GROMACS development team, led by
8  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
9  * and including many others, as listed in the AUTHORS file in the
10  * top-level source directory and at http://www.gromacs.org.
11  *
12  * GROMACS is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public License
14  * as published by the Free Software Foundation; either version 2.1
15  * of the License, or (at your option) any later version.
16  *
17  * GROMACS is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with GROMACS; if not, see
24  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
26  *
27  * If you want to redistribute modifications to GROMACS, please
28  * consider that scientific software is very special. Version
29  * control is crucial - bugs must be traceable. We will be happy to
30  * consider code for inclusion in the official distribution, but
31  * derived work must not be called official GROMACS. Details are found
32  * in the README & COPYING files - if they are missing, get the
33  * official version at http://www.gromacs.org.
34  *
35  * To help us fund GROMACS development, we humbly ask that you cite
36  * the research papers on the package. Check out http://www.gromacs.org.
37  */
38 #include "gmxpre.h"
39
40 #include "wallcycle.h"
41
42 #include "config.h"
43
44 #include <cstdlib>
45
46 #include <array>
47 #include <vector>
48
49 #include "gromacs/math/functions.h"
50 #include "gromacs/mdtypes/commrec.h"
51 #include "gromacs/timing/cyclecounter.h"
52 #include "gromacs/timing/gpu_timing.h"
53 #include "gromacs/timing/wallcyclereporting.h"
54 #include "gromacs/utility/cstringutil.h"
55 #include "gromacs/utility/gmxassert.h"
56 #include "gromacs/utility/gmxmpi.h"
57 #include "gromacs/utility/logger.h"
58 #include "gromacs/utility/smalloc.h"
59 #include "gromacs/utility/snprintf.h"
60
61 static const bool useCycleSubcounters = GMX_CYCLE_SUBCOUNTERS;
62
63 #ifndef DEBUG_WCYCLE
64 /*! \brief Enables consistency checking for the counters.
65  *
66  * If the macro is set to 1, code checks if you stop a counter different from the last
67  * one that was opened and if you do nest too deep.
68  */
69 #    define DEBUG_WCYCLE 0
70 #endif
71 //! Whether wallcycle debugging is enabled
72 constexpr bool gmx_unused enableWallcycleDebug = (DEBUG_WCYCLE != 0);
73 //! True if only the master rank should print debugging output
74 constexpr bool gmx_unused onlyMasterDebugPrints = true;
75 //! True if cycle counter nesting depth debuggin prints are enabled
76 constexpr bool gmx_unused debugPrintDepth = false /* enableWallcycleDebug */;
77
78 #if DEBUG_WCYCLE
79 #    include "gromacs/utility/fatalerror.h"
80 #endif
81
82 typedef struct
83 {
84     int          n;
85     gmx_cycles_t c;
86     gmx_cycles_t start;
87 } wallcc_t;
88
89 struct gmx_wallcycle
90 {
91     wallcc_t* wcc;
92     /* did we detect one or more invalid cycle counts */
93     gmx_bool haveInvalidCount;
94     /* variables for testing/debugging */
95     gmx_bool  wc_barrier;
96     wallcc_t* wcc_all;
97     int       wc_depth;
98 #if DEBUG_WCYCLE
99 #    define DEPTH_MAX 6
100     int  counterlist[DEPTH_MAX];
101     int  count_depth;
102     bool isMasterRank;
103 #endif
104     int          ewc_prev;
105     gmx_cycles_t cycle_prev;
106     int64_t      reset_counters;
107 #if GMX_MPI
108     MPI_Comm mpi_comm_mygroup;
109 #endif
110     wallcc_t* wcsc;
111 };
112
113 /* Each name should not exceed 19 printing characters
114    (ie. terminating null can be twentieth) */
115 static const char* wcn[ewcNR] = { "Run",
116                                   "Step",
117                                   "PP during PME",
118                                   "Domain decomp.",
119                                   "DD comm. load",
120                                   "DD comm. bounds",
121                                   "Vsite constr.",
122                                   "Send X to PME",
123                                   "Neighbor search",
124                                   "Launch GPU ops.",
125                                   "Comm. coord.",
126                                   "Force",
127                                   "Wait + Comm. F",
128                                   "PME mesh",
129                                   "PME redist. X/F",
130                                   "PME spread",
131                                   "PME gather",
132                                   "PME 3D-FFT",
133                                   "PME 3D-FFT Comm.",
134                                   "PME solve LJ",
135                                   "PME solve Elec",
136                                   "PME wait for PP",
137                                   "Wait + Recv. PME F",
138                                   "Wait PME GPU spread",
139                                   "PME 3D-FFT",
140                                   "PME solve", /* the strings for FFT/solve are repeated here for mixed mode counters */
141                                   "Wait PME GPU gather",
142                                   "Wait Bonded GPU",
143                                   "Reduce GPU PME F",
144                                   "Wait GPU NB nonloc.",
145                                   "Wait GPU NB local",
146                                   "Wait GPU state copy",
147                                   "NB X/F buffer ops.",
148                                   "Vsite spread",
149                                   "COM pull force",
150                                   "AWH",
151                                   "Write traj.",
152                                   "Update",
153                                   "Constraints",
154                                   "Comm. energies",
155                                   "Enforced rotation",
156                                   "Add rot. forces",
157                                   "Position swapping",
158                                   "IMD",
159                                   "Test" };
160
161 static const char* wcsn[ewcsNR] = {
162     "DD redist.",
163     "DD NS grid + sort",
164     "DD setup comm.",
165     "DD make top.",
166     "DD make constr.",
167     "DD top. other",
168     "DD GPU ops.",
169     "NS grid local",
170     "NS grid non-loc.",
171     "NS search local",
172     "NS search non-loc.",
173     "Bonded F",
174     "Bonded-FEP F",
175     "Restraints F",
176     "Listed buffer ops.",
177     "Nonbonded pruning",
178     "Nonbonded F kernel",
179     "Nonbonded F clear",
180     "Nonbonded FEP",
181     "Launch NB GPU tasks",
182     "Launch Bonded GPU tasks",
183     "Launch PME GPU tasks",
184     "Launch state copy",
185     "Ewald F correction",
186     "NB X buffer ops.",
187     "NB F buffer ops.",
188     "Clear force buffer",
189     "Launch GPU NB X buffer ops.",
190     "Launch GPU NB F buffer ops.",
191     "Launch GPU Comm. coord.",
192     "Launch GPU Comm. force.",
193     "Launch GPU update",
194     "Test subcounter",
195 };
196
197 /* PME GPU timing events' names - correspond to the enum in the gpu_timing.h */
198 static const char* PMEStageNames[] = {
199     "PME spline", "PME spread",     "PME spline + spread", "PME 3D-FFT r2c",
200     "PME solve",  "PME 3D-FFT c2r", "PME gather",
201 };
202
203 gmx_bool wallcycle_have_counter()
204 {
205     return gmx_cycles_have_counter();
206 }
207
208 gmx_wallcycle_t wallcycle_init(FILE* fplog, int resetstep, t_commrec gmx_unused* cr)
209 {
210     gmx_wallcycle_t wc;
211
212
213     if (!wallcycle_have_counter())
214     {
215         return nullptr;
216     }
217
218     snew(wc, 1);
219
220     wc->haveInvalidCount = FALSE;
221     wc->wc_barrier       = FALSE;
222     wc->wcc_all          = nullptr;
223     wc->wc_depth         = 0;
224     wc->ewc_prev         = -1;
225     wc->reset_counters   = resetstep;
226
227
228 #if GMX_MPI
229     if (PAR(cr) && getenv("GMX_CYCLE_BARRIER") != nullptr)
230     {
231         if (fplog)
232         {
233             fprintf(fplog, "\nWill call MPI_Barrier before each cycle start/stop call\n\n");
234         }
235         wc->wc_barrier       = TRUE;
236         wc->mpi_comm_mygroup = cr->mpi_comm_mygroup;
237     }
238 #endif
239
240     snew(wc->wcc, ewcNR);
241     if (getenv("GMX_CYCLE_ALL") != nullptr)
242     {
243         if (fplog)
244         {
245             fprintf(fplog, "\nWill time all the code during the run\n\n");
246         }
247         snew(wc->wcc_all, ewcNR * ewcNR);
248     }
249
250     if (useCycleSubcounters)
251     {
252         snew(wc->wcsc, ewcsNR);
253     }
254
255 #if DEBUG_WCYCLE
256     wc->count_depth  = 0;
257     wc->isMasterRank = MASTER(cr);
258 #endif
259
260     return wc;
261 }
262
263 void wallcycle_destroy(gmx_wallcycle_t wc)
264 {
265     if (wc == nullptr)
266     {
267         return;
268     }
269
270     if (wc->wcc != nullptr)
271     {
272         sfree(wc->wcc);
273     }
274     if (wc->wcc_all != nullptr)
275     {
276         sfree(wc->wcc_all);
277     }
278     if (wc->wcsc != nullptr)
279     {
280         sfree(wc->wcsc);
281     }
282     sfree(wc);
283 }
284
285 static void wallcycle_all_start(gmx_wallcycle_t wc, int ewc, gmx_cycles_t cycle)
286 {
287     wc->ewc_prev   = ewc;
288     wc->cycle_prev = cycle;
289 }
290
291 static void wallcycle_all_stop(gmx_wallcycle_t wc, int ewc, gmx_cycles_t cycle)
292 {
293     wc->wcc_all[wc->ewc_prev * ewcNR + ewc].n += 1;
294     wc->wcc_all[wc->ewc_prev * ewcNR + ewc].c += cycle - wc->cycle_prev;
295 }
296
297
298 #if DEBUG_WCYCLE
299 static void debug_start_check(gmx_wallcycle_t wc, int ewc)
300 {
301     if (wc->count_depth < 0 || wc->count_depth >= DEPTH_MAX)
302     {
303         gmx_fatal(FARGS, "wallcycle counter depth out of range: %d", wc->count_depth + 1);
304     }
305     wc->counterlist[wc->count_depth] = ewc;
306     wc->count_depth++;
307
308     if (debugPrintDepth && (!onlyMasterDebugPrints || wc->isMasterRank))
309     {
310         std::string indentStr(4 * wc->count_depth, ' ');
311         fprintf(stderr, "%swcycle_start depth %d, %s\n", indentStr.c_str(), wc->count_depth, wcn[ewc]);
312     }
313 }
314
315 static void debug_stop_check(gmx_wallcycle_t wc, int ewc)
316 {
317     if (debugPrintDepth && (!onlyMasterDebugPrints || wc->isMasterRank))
318     {
319         std::string indentStr(4 * wc->count_depth, ' ');
320         fprintf(stderr, "%swcycle_stop  depth %d, %s\n", indentStr.c_str(), wc->count_depth, wcn[ewc]);
321     }
322
323     wc->count_depth--;
324
325     if (wc->count_depth < 0)
326     {
327         gmx_fatal(FARGS, "wallcycle counter depth out of range when stopping %s: %d", wcn[ewc], wc->count_depth);
328     }
329     if (wc->counterlist[wc->count_depth] != ewc)
330     {
331         gmx_fatal(FARGS,
332                   "wallcycle mismatch at stop, start %s, stop %s",
333                   wcn[wc->counterlist[wc->count_depth]],
334                   wcn[ewc]);
335     }
336 }
337 #endif
338
339 void wallcycle_start(gmx_wallcycle_t wc, int ewc)
340 {
341     gmx_cycles_t cycle;
342
343     if (wc == nullptr)
344     {
345         return;
346     }
347
348 #if GMX_MPI
349     if (wc->wc_barrier)
350     {
351         MPI_Barrier(wc->mpi_comm_mygroup);
352     }
353 #endif
354
355 #if DEBUG_WCYCLE
356     debug_start_check(wc, ewc);
357 #endif
358
359     cycle              = gmx_cycles_read();
360     wc->wcc[ewc].start = cycle;
361     if (wc->wcc_all != nullptr)
362     {
363         wc->wc_depth++;
364         if (ewc == ewcRUN)
365         {
366             wallcycle_all_start(wc, ewc, cycle);
367         }
368         else if (wc->wc_depth == 3)
369         {
370             wallcycle_all_stop(wc, ewc, cycle);
371         }
372     }
373 }
374
375 void wallcycle_increment_event_count(gmx_wallcycle_t wc, int ewc)
376 {
377     if (wc == nullptr)
378     {
379         return;
380     }
381     wc->wcc[ewc].n++;
382 }
383
384 void wallcycle_start_nocount(gmx_wallcycle_t wc, int ewc)
385 {
386     if (wc == nullptr)
387     {
388         return;
389     }
390
391     wallcycle_start(wc, ewc);
392     wc->wcc[ewc].n--;
393 }
394
395 double wallcycle_stop(gmx_wallcycle_t wc, int ewc)
396 {
397     gmx_cycles_t cycle, last;
398
399     if (wc == nullptr)
400     {
401         return 0;
402     }
403
404 #if GMX_MPI
405     if (wc->wc_barrier)
406     {
407         MPI_Barrier(wc->mpi_comm_mygroup);
408     }
409 #endif
410
411 #if DEBUG_WCYCLE
412     debug_stop_check(wc, ewc);
413 #endif
414
415     /* When processes or threads migrate between cores, the cycle counting
416      * can get messed up if the cycle counter on different cores are not
417      * synchronized. When this happens we expect both large negative and
418      * positive cycle differences. We can detect negative cycle differences.
419      * Detecting too large positive counts if difficult, since count can be
420      * large, especially for ewcRUN. If we detect a negative count,
421      * we will not print the cycle accounting table.
422      */
423     cycle = gmx_cycles_read();
424     if (cycle >= wc->wcc[ewc].start)
425     {
426         last = cycle - wc->wcc[ewc].start;
427     }
428     else
429     {
430         last                 = 0;
431         wc->haveInvalidCount = TRUE;
432     }
433     wc->wcc[ewc].c += last;
434     wc->wcc[ewc].n++;
435     if (wc->wcc_all)
436     {
437         wc->wc_depth--;
438         if (ewc == ewcRUN)
439         {
440             wallcycle_all_stop(wc, ewc, cycle);
441         }
442         else if (wc->wc_depth == 2)
443         {
444             wallcycle_all_start(wc, ewc, cycle);
445         }
446     }
447
448     return last;
449 }
450
451 void wallcycle_get(gmx_wallcycle_t wc, int ewc, int* n, double* c)
452 {
453     *n = wc->wcc[ewc].n;
454     *c = static_cast<double>(wc->wcc[ewc].c);
455 }
456
457 void wallcycle_reset_all(gmx_wallcycle_t wc)
458 {
459     int i;
460
461     if (wc == nullptr)
462     {
463         return;
464     }
465
466     for (i = 0; i < ewcNR; i++)
467     {
468         wc->wcc[i].n = 0;
469         wc->wcc[i].c = 0;
470     }
471     wc->haveInvalidCount = FALSE;
472
473     if (wc->wcc_all)
474     {
475         for (i = 0; i < ewcNR * ewcNR; i++)
476         {
477             wc->wcc_all[i].n = 0;
478             wc->wcc_all[i].c = 0;
479         }
480     }
481     if (wc->wcsc)
482     {
483         for (i = 0; i < ewcsNR; i++)
484         {
485             wc->wcsc[i].n = 0;
486             wc->wcsc[i].c = 0;
487         }
488     }
489 }
490
491 static gmx_bool is_pme_counter(int ewc)
492 {
493     return (ewc >= ewcPMEMESH && ewc <= ewcPMEWAITCOMM);
494 }
495
496 static gmx_bool is_pme_subcounter(int ewc)
497 {
498     return (ewc >= ewcPME_REDISTXF && ewc < ewcPMEWAITCOMM);
499 }
500
501 /* Subtract counter ewc_sub timed inside a timing block for ewc_main */
502 static void subtract_cycles(wallcc_t* wcc, int ewc_main, int ewc_sub)
503 {
504     if (wcc[ewc_sub].n > 0)
505     {
506         if (wcc[ewc_main].c >= wcc[ewc_sub].c)
507         {
508             wcc[ewc_main].c -= wcc[ewc_sub].c;
509         }
510         else
511         {
512             /* Something is wrong with the cycle counting */
513             wcc[ewc_main].c = 0;
514         }
515     }
516 }
517
518 void wallcycle_scale_by_num_threads(gmx_wallcycle_t wc, bool isPmeRank, int nthreads_pp, int nthreads_pme)
519 {
520     if (wc == nullptr)
521     {
522         return;
523     }
524
525     for (int i = 0; i < ewcNR; i++)
526     {
527         if (is_pme_counter(i) || (i == ewcRUN && isPmeRank))
528         {
529             wc->wcc[i].c *= nthreads_pme;
530
531             if (wc->wcc_all)
532             {
533                 for (int j = 0; j < ewcNR; j++)
534                 {
535                     wc->wcc_all[i * ewcNR + j].c *= nthreads_pme;
536                 }
537             }
538         }
539         else
540         {
541             wc->wcc[i].c *= nthreads_pp;
542
543             if (wc->wcc_all)
544             {
545                 for (int j = 0; j < ewcNR; j++)
546                 {
547                     wc->wcc_all[i * ewcNR + j].c *= nthreads_pp;
548                 }
549             }
550         }
551     }
552     if (useCycleSubcounters && wc->wcsc && !isPmeRank)
553     {
554         for (int i = 0; i < ewcsNR; i++)
555         {
556             wc->wcsc[i].c *= nthreads_pp;
557         }
558     }
559 }
560
561 /* TODO Make an object for this function to return, containing some
562  * vectors of something like wallcc_t for the summed wcc, wcc_all and
563  * wcsc, AND the original wcc for rank 0.
564  *
565  * The GPU timing is reported only for rank 0, so we want to preserve
566  * the original wcycle on that rank. Rank 0 also reports the global
567  * counts before that, so needs something to contain the global data
568  * without over-writing the rank-0 data. The current implementation
569  * uses cycles_sum to manage this, which works OK now because wcsc and
570  * wcc_all are unused by the GPU reporting, but it is not satisfactory
571  * for the future. Also, there's no need for MPI_Allreduce, since
572  * only MASTERRANK uses any of the results. */
573 WallcycleCounts wallcycle_sum(const t_commrec* cr, gmx_wallcycle_t wc)
574 {
575     WallcycleCounts cycles_sum;
576     wallcc_t*       wcc;
577     double          cycles[int(ewcNR) + int(ewcsNR)];
578 #if GMX_MPI
579     double cycles_n[int(ewcNR) + int(ewcsNR) + 1];
580 #endif
581     int i;
582     int nsum;
583
584     if (wc == nullptr)
585     {
586         /* Default construction of std::array of non-class T can leave
587            the values indeterminate, just like a C array */
588         cycles_sum.fill(0);
589         return cycles_sum;
590     }
591
592     wcc = wc->wcc;
593
594     subtract_cycles(wcc, ewcDOMDEC, ewcDDCOMMLOAD);
595     subtract_cycles(wcc, ewcDOMDEC, ewcDDCOMMBOUND);
596
597     subtract_cycles(wcc, ewcPME_FFT, ewcPME_FFTCOMM);
598
599     if (cr->npmenodes == 0)
600     {
601         /* All nodes do PME (or no PME at all) */
602         subtract_cycles(wcc, ewcFORCE, ewcPMEMESH);
603     }
604     else
605     {
606         /* The are PME-only nodes */
607         if (wcc[ewcPMEMESH].n > 0)
608         {
609             /* This must be a PME only node, calculate the Wait + Comm. time */
610             GMX_ASSERT(wcc[ewcRUN].c >= wcc[ewcPMEMESH].c,
611                        "Total run ticks must be greater than PME-only ticks");
612             wcc[ewcPMEWAITCOMM].c = wcc[ewcRUN].c - wcc[ewcPMEMESH].c;
613         }
614     }
615
616     /* Store the cycles in a double buffer for summing */
617     for (i = 0; i < ewcNR; i++)
618     {
619 #if GMX_MPI
620         cycles_n[i] = static_cast<double>(wcc[i].n);
621 #endif
622         cycles[i] = static_cast<double>(wcc[i].c);
623     }
624     nsum = ewcNR;
625     if (wc->wcsc)
626     {
627         for (i = 0; i < ewcsNR; i++)
628         {
629 #if GMX_MPI
630             cycles_n[ewcNR + i] = static_cast<double>(wc->wcsc[i].n);
631 #endif
632             cycles[ewcNR + i] = static_cast<double>(wc->wcsc[i].c);
633         }
634         nsum += ewcsNR;
635     }
636
637 #if GMX_MPI
638     if (cr->nnodes > 1)
639     {
640         double buf[int(ewcNR) + int(ewcsNR) + 1];
641
642         // TODO this code is used only at the end of the run, so we
643         // can just do a simple reduce of haveInvalidCount in
644         // wallcycle_print, and avoid bugs
645         cycles_n[nsum] = (wc->haveInvalidCount ? 1 : 0);
646         // TODO Use MPI_Reduce
647         MPI_Allreduce(cycles_n, buf, nsum + 1, MPI_DOUBLE, MPI_MAX, cr->mpi_comm_mysim);
648         for (i = 0; i < ewcNR; i++)
649         {
650             wcc[i].n = gmx::roundToInt(buf[i]);
651         }
652         wc->haveInvalidCount = (buf[nsum] > 0);
653         if (wc->wcsc)
654         {
655             for (i = 0; i < ewcsNR; i++)
656             {
657                 wc->wcsc[i].n = gmx::roundToInt(buf[ewcNR + i]);
658             }
659         }
660
661         // TODO Use MPI_Reduce
662         MPI_Allreduce(cycles, cycles_sum.data(), nsum, MPI_DOUBLE, MPI_SUM, cr->mpi_comm_mysim);
663
664         if (wc->wcc_all != nullptr)
665         {
666             double *buf_all, *cyc_all;
667
668             snew(cyc_all, ewcNR * ewcNR);
669             snew(buf_all, ewcNR * ewcNR);
670             for (i = 0; i < ewcNR * ewcNR; i++)
671             {
672                 cyc_all[i] = wc->wcc_all[i].c;
673             }
674             // TODO Use MPI_Reduce
675             MPI_Allreduce(cyc_all, buf_all, ewcNR * ewcNR, MPI_DOUBLE, MPI_SUM, cr->mpi_comm_mysim);
676             for (i = 0; i < ewcNR * ewcNR; i++)
677             {
678                 wc->wcc_all[i].c = static_cast<gmx_cycles_t>(buf_all[i]);
679             }
680             sfree(buf_all);
681             sfree(cyc_all);
682         }
683     }
684     else
685 #endif
686     {
687         for (i = 0; i < nsum; i++)
688         {
689             cycles_sum[i] = cycles[i];
690         }
691     }
692
693     return cycles_sum;
694 }
695
696 static void
697 print_cycles(FILE* fplog, double c2t, const char* name, int nnodes, int nthreads, int ncalls, double c_sum, double tot)
698 {
699     char   nnodes_str[STRLEN];
700     char   nthreads_str[STRLEN];
701     char   ncalls_str[STRLEN];
702     double wallt;
703     double percentage = (tot > 0.) ? (100. * c_sum / tot) : 0.;
704
705     if (c_sum > 0)
706     {
707         if (ncalls > 0)
708         {
709             snprintf(ncalls_str, sizeof(ncalls_str), "%10d", ncalls);
710             if (nnodes < 0)
711             {
712                 snprintf(nnodes_str, sizeof(nnodes_str), "N/A");
713             }
714             else
715             {
716                 snprintf(nnodes_str, sizeof(nnodes_str), "%4d", nnodes);
717             }
718             if (nthreads < 0)
719             {
720                 snprintf(nthreads_str, sizeof(nthreads_str), "N/A");
721             }
722             else
723             {
724                 snprintf(nthreads_str, sizeof(nthreads_str), "%4d", nthreads);
725             }
726         }
727         else
728         {
729             nnodes_str[0]   = 0;
730             nthreads_str[0] = 0;
731             ncalls_str[0]   = 0;
732         }
733         /* Convert the cycle count to wallclock time for this task */
734         wallt = c_sum * c2t;
735
736         fprintf(fplog,
737                 " %-19.19s %4s %4s %10s  %10.3f %14.3f %5.1f\n",
738                 name,
739                 nnodes_str,
740                 nthreads_str,
741                 ncalls_str,
742                 wallt,
743                 c_sum * 1e-9,
744                 percentage);
745     }
746 }
747
748 static void print_gputimes(FILE* fplog, const char* name, int n, double t, double tot_t)
749 {
750     char num[11];
751     char avg_perf[11];
752
753     if (n > 0)
754     {
755         snprintf(num, sizeof(num), "%10d", n);
756         snprintf(avg_perf, sizeof(avg_perf), "%10.3f", t / n);
757     }
758     else
759     {
760         sprintf(num, "          ");
761         sprintf(avg_perf, "          ");
762     }
763     if (t != tot_t && tot_t > 0)
764     {
765         fprintf(fplog, " %-29s %10s%12.3f   %s   %5.1f\n", name, num, t / 1000, avg_perf, 100 * t / tot_t);
766     }
767     else
768     {
769         fprintf(fplog, " %-29s %10s%12.3f   %s   %5.1f\n", name, "", t / 1000, avg_perf, 100.0);
770     }
771 }
772
773 static void print_header(FILE* fplog, int nrank_pp, int nth_pp, int nrank_pme, int nth_pme)
774 {
775     int nrank_tot = nrank_pp + nrank_pme;
776     if (0 == nrank_pme)
777     {
778         fprintf(fplog, "On %d MPI rank%s", nrank_tot, nrank_tot == 1 ? "" : "s");
779         if (nth_pp > 1)
780         {
781             fprintf(fplog, ", each using %d OpenMP threads", nth_pp);
782         }
783         /* Don't report doing PP+PME, because we can't tell here if
784          * this is RF, etc. */
785     }
786     else
787     {
788         fprintf(fplog, "On %d MPI rank%s doing PP", nrank_pp, nrank_pp == 1 ? "" : "s");
789         if (nth_pp > 1)
790         {
791             fprintf(fplog, ",%s using %d OpenMP threads", nrank_pp > 1 ? " each" : "", nth_pp);
792         }
793         fprintf(fplog, ", and\non %d MPI rank%s doing PME", nrank_pme, nrank_pme == 1 ? "" : "s");
794         if (nth_pme > 1)
795         {
796             fprintf(fplog, ",%s using %d OpenMP threads", nrank_pme > 1 ? " each" : "", nth_pme);
797         }
798     }
799
800     fprintf(fplog, "\n\n");
801     fprintf(fplog, " Computing:          Num   Num      Call    Wall time         Giga-Cycles\n");
802     fprintf(fplog, "                     Ranks Threads  Count      (s)         total sum    %%\n");
803 }
804
805
806 void wallcycle_print(FILE*                            fplog,
807                      const gmx::MDLogger&             mdlog,
808                      int                              nnodes,
809                      int                              npme,
810                      int                              nth_pp,
811                      int                              nth_pme,
812                      double                           realtime,
813                      gmx_wallcycle_t                  wc,
814                      const WallcycleCounts&           cyc_sum,
815                      const gmx_wallclock_gpu_nbnxn_t* gpu_nbnxn_t,
816                      const gmx_wallclock_gpu_pme_t*   gpu_pme_t)
817 {
818     double      tot, tot_for_pp, tot_for_rest, tot_cpu_overlap, gpu_cpu_ratio;
819     double      c2t, c2t_pp, c2t_pme = 0;
820     int         i, j, npp, nth_tot;
821     char        buf[STRLEN];
822     const char* hline =
823             "-----------------------------------------------------------------------------";
824
825     if (wc == nullptr)
826     {
827         return;
828     }
829
830     GMX_ASSERT(nth_pp > 0, "Number of particle-particle threads must be >0");
831     GMX_ASSERT(nth_pme > 0, "Number of PME threads must be >0");
832     GMX_ASSERT(nnodes > 0, "Number of nodes must be >0");
833     GMX_ASSERT(npme >= 0, "Number of PME nodes cannot be negative");
834     npp = nnodes - npme;
835     /* npme is the number of PME-only ranks used, and we always do PP work */
836     GMX_ASSERT(npp > 0, "Number of particle-particle nodes must be >0");
837
838     nth_tot = npp * nth_pp + npme * nth_pme;
839
840     /* When using PME-only nodes, the next line is valid for both
841        PP-only and PME-only nodes because they started ewcRUN at the
842        same time. */
843     tot        = cyc_sum[ewcRUN];
844     tot_for_pp = 0;
845
846     if (tot <= 0.0)
847     {
848         /* TODO This is heavy handed, but until someone reworks the
849            code so that it is provably robust with respect to
850            non-positive values for all possible timer and cycle
851            counters, there is less value gained from printing whatever
852            timing data might still be sensible for some non-Jenkins
853            run, than is lost from diagnosing Jenkins FP exceptions on
854            runs about whose execution time we don't care. */
855         GMX_LOG(mdlog.warning)
856                 .asParagraph()
857                 .appendTextFormatted(
858                         "WARNING: A total of %f CPU cycles was recorded, so mdrun cannot print a "
859                         "time accounting",
860                         tot);
861         return;
862     }
863
864     if (wc->haveInvalidCount)
865     {
866         GMX_LOG(mdlog.warning)
867                 .asParagraph()
868                 .appendText(
869                         "NOTE: Detected invalid cycle counts, probably because threads moved "
870                         "between CPU cores that do not have synchronized cycle counters. Will not "
871                         "print the cycle accounting.");
872         return;
873     }
874
875
876     /* Conversion factor from cycles to seconds */
877     c2t    = realtime / tot;
878     c2t_pp = c2t * nth_tot / static_cast<double>(npp * nth_pp);
879     if (npme > 0)
880     {
881         c2t_pme = c2t * nth_tot / static_cast<double>(npme * nth_pme);
882     }
883     else
884     {
885         c2t_pme = 0;
886     }
887
888     fprintf(fplog, "\n     R E A L   C Y C L E   A N D   T I M E   A C C O U N T I N G\n\n");
889
890     print_header(fplog, npp, nth_pp, npme, nth_pme);
891
892     fprintf(fplog, "%s\n", hline);
893     for (i = ewcPPDURINGPME + 1; i < ewcNR; i++)
894     {
895         if (is_pme_subcounter(i))
896         {
897             /* Do not count these at all */
898         }
899         else if (npme > 0 && is_pme_counter(i))
900         {
901             /* Print timing information for PME-only nodes, but add an
902              * asterisk so the reader of the table can know that the
903              * walltimes are not meant to add up. The asterisk still
904              * fits in the required maximum of 19 characters. */
905             char buffer[STRLEN];
906             snprintf(buffer, STRLEN, "%s *", wcn[i]);
907             print_cycles(fplog, c2t_pme, buffer, npme, nth_pme, wc->wcc[i].n, cyc_sum[i], tot);
908         }
909         else
910         {
911             /* Print timing information when it is for a PP or PP+PME
912                node */
913             print_cycles(fplog, c2t_pp, wcn[i], npp, nth_pp, wc->wcc[i].n, cyc_sum[i], tot);
914             tot_for_pp += cyc_sum[i];
915         }
916     }
917     if (wc->wcc_all != nullptr)
918     {
919         for (i = 0; i < ewcNR; i++)
920         {
921             for (j = 0; j < ewcNR; j++)
922             {
923                 snprintf(buf, 20, "%-9.9s %-9.9s", wcn[i], wcn[j]);
924                 print_cycles(fplog,
925                              c2t_pp,
926                              buf,
927                              npp,
928                              nth_pp,
929                              wc->wcc_all[i * ewcNR + j].n,
930                              wc->wcc_all[i * ewcNR + j].c,
931                              tot);
932             }
933         }
934     }
935     tot_for_rest = tot * npp * nth_pp / static_cast<double>(nth_tot);
936     print_cycles(fplog, c2t_pp, "Rest", npp, nth_pp, -1, tot_for_rest - tot_for_pp, tot);
937     fprintf(fplog, "%s\n", hline);
938     print_cycles(fplog, c2t, "Total", npp, nth_pp, -1, tot, tot);
939     fprintf(fplog, "%s\n", hline);
940
941     if (npme > 0)
942     {
943         fprintf(fplog,
944                 "(*) Note that with separate PME ranks, the walltime column actually sums to\n"
945                 "    twice the total reported, but the cycle count total and %% are correct.\n"
946                 "%s\n",
947                 hline);
948     }
949
950     if (wc->wcc[ewcPMEMESH].n > 0)
951     {
952         // A workaround to not print breakdown when no subcounters were recorded.
953         // TODO: figure out and record PME GPU counters (what to do with the waiting ones?)
954         std::vector<int> validPmeSubcounterIndices;
955         for (i = ewcPPDURINGPME + 1; i < ewcNR; i++)
956         {
957             if (is_pme_subcounter(i) && wc->wcc[i].n > 0)
958             {
959                 validPmeSubcounterIndices.push_back(i);
960             }
961         }
962
963         if (!validPmeSubcounterIndices.empty())
964         {
965             fprintf(fplog, " Breakdown of PME mesh computation\n");
966             fprintf(fplog, "%s\n", hline);
967             for (auto i : validPmeSubcounterIndices)
968             {
969                 print_cycles(fplog,
970                              npme > 0 ? c2t_pme : c2t_pp,
971                              wcn[i],
972                              npme > 0 ? npme : npp,
973                              nth_pme,
974                              wc->wcc[i].n,
975                              cyc_sum[i],
976                              tot);
977             }
978             fprintf(fplog, "%s\n", hline);
979         }
980     }
981
982     if (useCycleSubcounters && wc->wcsc)
983     {
984         fprintf(fplog, " Breakdown of PP computation\n");
985         fprintf(fplog, "%s\n", hline);
986         for (i = 0; i < ewcsNR; i++)
987         {
988             print_cycles(fplog, c2t_pp, wcsn[i], npp, nth_pp, wc->wcsc[i].n, cyc_sum[ewcNR + i], tot);
989         }
990         fprintf(fplog, "%s\n", hline);
991     }
992
993     /* print GPU timing summary */
994     double tot_gpu = 0.0;
995     if (gpu_pme_t)
996     {
997         for (size_t k = 0; k < gtPME_EVENT_COUNT; k++)
998         {
999             tot_gpu += gpu_pme_t->timing[k].t;
1000         }
1001     }
1002     if (gpu_nbnxn_t)
1003     {
1004         const char* k_log_str[2][2] = { { "Nonbonded F kernel", "Nonbonded F+ene k." },
1005                                         { "Nonbonded F+prune k.", "Nonbonded F+ene+prune k." } };
1006         tot_gpu += gpu_nbnxn_t->pl_h2d_t + gpu_nbnxn_t->nb_h2d_t + gpu_nbnxn_t->nb_d2h_t;
1007
1008         /* add up the kernel timings */
1009         for (i = 0; i < 2; i++)
1010         {
1011             for (j = 0; j < 2; j++)
1012             {
1013                 tot_gpu += gpu_nbnxn_t->ktime[i][j].t;
1014             }
1015         }
1016         tot_gpu += gpu_nbnxn_t->pruneTime.t;
1017
1018         tot_cpu_overlap = wc->wcc[ewcFORCE].c;
1019         if (wc->wcc[ewcPMEMESH].n > 0)
1020         {
1021             tot_cpu_overlap += wc->wcc[ewcPMEMESH].c;
1022         }
1023         tot_cpu_overlap *= realtime * 1000 / tot; /* convert s to ms */
1024
1025         fprintf(fplog, "\n GPU timings\n%s\n", hline);
1026         fprintf(fplog,
1027                 " Computing:                         Count  Wall t (s)      ms/step       %c\n",
1028                 '%');
1029         fprintf(fplog, "%s\n", hline);
1030         print_gputimes(fplog, "Pair list H2D", gpu_nbnxn_t->pl_h2d_c, gpu_nbnxn_t->pl_h2d_t, tot_gpu);
1031         print_gputimes(fplog, "X / q H2D", gpu_nbnxn_t->nb_c, gpu_nbnxn_t->nb_h2d_t, tot_gpu);
1032
1033         for (i = 0; i < 2; i++)
1034         {
1035             for (j = 0; j < 2; j++)
1036             {
1037                 if (gpu_nbnxn_t->ktime[i][j].c)
1038                 {
1039                     print_gputimes(fplog,
1040                                    k_log_str[i][j],
1041                                    gpu_nbnxn_t->ktime[i][j].c,
1042                                    gpu_nbnxn_t->ktime[i][j].t,
1043                                    tot_gpu);
1044                 }
1045             }
1046         }
1047         if (gpu_pme_t)
1048         {
1049             for (size_t k = 0; k < gtPME_EVENT_COUNT; k++)
1050             {
1051                 if (gpu_pme_t->timing[k].c)
1052                 {
1053                     print_gputimes(
1054                             fplog, PMEStageNames[k], gpu_pme_t->timing[k].c, gpu_pme_t->timing[k].t, tot_gpu);
1055                 }
1056             }
1057         }
1058         if (gpu_nbnxn_t->pruneTime.c)
1059         {
1060             print_gputimes(fplog, "Pruning kernel", gpu_nbnxn_t->pruneTime.c, gpu_nbnxn_t->pruneTime.t, tot_gpu);
1061         }
1062         print_gputimes(fplog, "F D2H", gpu_nbnxn_t->nb_c, gpu_nbnxn_t->nb_d2h_t, tot_gpu);
1063         fprintf(fplog, "%s\n", hline);
1064         print_gputimes(fplog, "Total ", gpu_nbnxn_t->nb_c, tot_gpu, tot_gpu);
1065         fprintf(fplog, "%s\n", hline);
1066         if (gpu_nbnxn_t->dynamicPruneTime.c)
1067         {
1068             /* We print the dynamic pruning kernel timings after a separator
1069              * and avoid adding it to tot_gpu as this is not in the force
1070              * overlap. We print the fraction as relative to the rest.
1071              */
1072             print_gputimes(fplog,
1073                            "*Dynamic pruning",
1074                            gpu_nbnxn_t->dynamicPruneTime.c,
1075                            gpu_nbnxn_t->dynamicPruneTime.t,
1076                            tot_gpu);
1077             fprintf(fplog, "%s\n", hline);
1078         }
1079         gpu_cpu_ratio = tot_gpu / tot_cpu_overlap;
1080         if (gpu_nbnxn_t->nb_c > 0 && wc->wcc[ewcFORCE].n > 0)
1081         {
1082             fprintf(fplog,
1083                     "\nAverage per-step force GPU/CPU evaluation time ratio: %.3f ms/%.3f ms = "
1084                     "%.3f\n",
1085                     tot_gpu / gpu_nbnxn_t->nb_c,
1086                     tot_cpu_overlap / wc->wcc[ewcFORCE].n,
1087                     gpu_cpu_ratio);
1088         }
1089
1090         /* only print notes related to CPU-GPU load balance with PME */
1091         if (wc->wcc[ewcPMEMESH].n > 0)
1092         {
1093             fprintf(fplog, "For optimal resource utilization this ratio should be close to 1\n");
1094
1095             /* print note if the imbalance is high with PME case in which
1096              * CPU-GPU load balancing is possible */
1097             if (gpu_cpu_ratio < 0.8 || gpu_cpu_ratio > 1.25)
1098             {
1099                 /* Only the sim master calls this function, so always print to stderr */
1100                 if (gpu_cpu_ratio < 0.8)
1101                 {
1102                     if (npp > 1)
1103                     {
1104                         /* The user could have used -notunepme,
1105                          * but we currently can't check that here.
1106                          */
1107                         GMX_LOG(mdlog.warning)
1108                                 .asParagraph()
1109                                 .appendText(
1110                                         "NOTE: The CPU has >25% more load than the GPU. This "
1111                                         "imbalance wastes\n"
1112                                         "      GPU resources. Maybe the domain decomposition "
1113                                         "limits the PME tuning.\n"
1114                                         "      In that case, try setting the DD grid manually "
1115                                         "(-dd) or lowering -dds.");
1116                     }
1117                     else
1118                     {
1119                         /* We should not end up here, unless the box is
1120                          * too small for increasing the cut-off for PME tuning.
1121                          */
1122                         GMX_LOG(mdlog.warning)
1123                                 .asParagraph()
1124                                 .appendText(
1125                                         "NOTE: The CPU has >25% more load than the GPU. This "
1126                                         "imbalance wastes\n"
1127                                         "      GPU resources.");
1128                     }
1129                 }
1130                 if (gpu_cpu_ratio > 1.25)
1131                 {
1132                     GMX_LOG(mdlog.warning)
1133                             .asParagraph()
1134                             .appendText(
1135                                     "NOTE: The GPU has >25% more load than the CPU. This imbalance "
1136                                     "wastes\n"
1137                                     "      CPU resources.");
1138                 }
1139             }
1140         }
1141     }
1142
1143     if (wc->wc_barrier)
1144     {
1145         GMX_LOG(mdlog.warning)
1146                 .asParagraph()
1147                 .appendText(
1148                         "MPI_Barrier was called before each cycle start/stop\n"
1149                         "call, so timings are not those of real runs.");
1150     }
1151
1152     if (wc->wcc[ewcNB_XF_BUF_OPS].n > 0 && (cyc_sum[ewcDOMDEC] > tot * 0.1 || cyc_sum[ewcNS] > tot * 0.1))
1153     {
1154         /* Only the sim master calls this function, so always print to stderr */
1155         if (wc->wcc[ewcDOMDEC].n == 0)
1156         {
1157             GMX_LOG(mdlog.warning)
1158                     .asParagraph()
1159                     .appendTextFormatted(
1160                             "NOTE: %d %% of the run time was spent in pair search,\n"
1161                             "      you might want to increase nstlist (this has no effect on "
1162                             "accuracy)\n",
1163                             gmx::roundToInt(100 * cyc_sum[ewcNS] / tot));
1164         }
1165         else
1166         {
1167             GMX_LOG(mdlog.warning)
1168                     .asParagraph()
1169                     .appendTextFormatted(
1170                             "NOTE: %d %% of the run time was spent in domain decomposition,\n"
1171                             "      %d %% of the run time was spent in pair search,\n"
1172                             "      you might want to increase nstlist (this has no effect on "
1173                             "accuracy)\n",
1174                             gmx::roundToInt(100 * cyc_sum[ewcDOMDEC] / tot),
1175                             gmx::roundToInt(100 * cyc_sum[ewcNS] / tot));
1176         }
1177     }
1178
1179     if (cyc_sum[ewcMoveE] > tot * 0.05)
1180     {
1181         GMX_LOG(mdlog.warning)
1182                 .asParagraph()
1183                 .appendTextFormatted(
1184                         "NOTE: %d %% of the run time was spent communicating energies,\n"
1185                         "      you might want to increase some nst* mdp options\n",
1186                         gmx::roundToInt(100 * cyc_sum[ewcMoveE] / tot));
1187     }
1188 }
1189
1190 extern int64_t wcycle_get_reset_counters(gmx_wallcycle_t wc)
1191 {
1192     if (wc == nullptr)
1193     {
1194         return -1;
1195     }
1196
1197     return wc->reset_counters;
1198 }
1199
1200 extern void wcycle_set_reset_counters(gmx_wallcycle_t wc, int64_t reset_counters)
1201 {
1202     if (wc == nullptr)
1203     {
1204         return;
1205     }
1206
1207     wc->reset_counters = reset_counters;
1208 }
1209
1210 void wallcycle_sub_start(gmx_wallcycle_t wc, int ewcs)
1211 {
1212     if (useCycleSubcounters && wc != nullptr)
1213     {
1214         wc->wcsc[ewcs].start = gmx_cycles_read();
1215     }
1216 }
1217
1218 void wallcycle_sub_start_nocount(gmx_wallcycle_t wc, int ewcs)
1219 {
1220     if (useCycleSubcounters && wc != nullptr)
1221     {
1222         wallcycle_sub_start(wc, ewcs);
1223         wc->wcsc[ewcs].n--;
1224     }
1225 }
1226
1227 void wallcycle_sub_stop(gmx_wallcycle_t wc, int ewcs)
1228 {
1229     if (useCycleSubcounters && wc != nullptr)
1230     {
1231         wc->wcsc[ewcs].c += gmx_cycles_read() - wc->wcsc[ewcs].start;
1232         wc->wcsc[ewcs].n++;
1233     }
1234 }