b0b6a70852ac3b6062152b532256c682027b66c7
[alexxy/gromacs.git] / src / gromacs / timing / wallcycle.c
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, by the GROMACS development team, led by
7  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
8  * and including many others, as listed in the AUTHORS file in the
9  * top-level source directory and at http://www.gromacs.org.
10  *
11  * GROMACS is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public License
13  * as published by the Free Software Foundation; either version 2.1
14  * of the License, or (at your option) any later version.
15  *
16  * GROMACS is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with GROMACS; if not, see
23  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
24  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
25  *
26  * If you want to redistribute modifications to GROMACS, please
27  * consider that scientific software is very special. Version
28  * control is crucial - bugs must be traceable. We will be happy to
29  * consider code for inclusion in the official distribution, but
30  * derived work must not be called official GROMACS. Details are found
31  * in the README & COPYING files - if they are missing, get the
32  * official version at http://www.gromacs.org.
33  *
34  * To help us fund GROMACS development, we humbly ask that you cite
35  * the research papers on the package. Check out http://www.gromacs.org.
36  */
37 #include "gmxpre.h"
38
39 #include "wallcycle.h"
40
41 #include "config.h"
42
43 #include <assert.h>
44 #include <stdlib.h>
45
46 #include "gromacs/legacyheaders/md_logging.h"
47 #include "gromacs/legacyheaders/types/commrec.h"
48 #include "gromacs/timing/cyclecounter.h"
49 #include "gromacs/timing/gpu_timing.h"
50 #include "gromacs/utility/cstringutil.h"
51 #include "gromacs/utility/gmxmpi.h"
52 #include "gromacs/utility/smalloc.h"
53 #include "gromacs/utility/snprintf.h"
54
55 /* DEBUG_WCYCLE adds consistency checking for the counters.
56  * It checks if you stop a counter different from the last
57  * one that was opened and if you do nest too deep.
58  */
59 /* #define DEBUG_WCYCLE */
60
61 #ifdef DEBUG_WCYCLE
62 #include "gromacs/utility/fatalerror.h"
63 #endif
64
65 typedef struct
66 {
67     int          n;
68     gmx_cycles_t c;
69     gmx_cycles_t start;
70     gmx_cycles_t last;
71 } wallcc_t;
72
73 typedef struct gmx_wallcycle
74 {
75     wallcc_t        *wcc;
76     /* variables for testing/debugging */
77     gmx_bool         wc_barrier;
78     wallcc_t        *wcc_all;
79     int              wc_depth;
80 #ifdef DEBUG_WCYCLE
81 #define DEPTH_MAX 6
82     int               counterlist[DEPTH_MAX];
83     int               count_depth;
84 #endif
85     int               ewc_prev;
86     gmx_cycles_t      cycle_prev;
87     gmx_int64_t       reset_counters;
88 #ifdef GMX_MPI
89     MPI_Comm          mpi_comm_mygroup;
90 #endif
91     int               nthreads_pp;
92     int               nthreads_pme;
93 #ifdef GMX_CYCLE_SUBCOUNTERS
94     wallcc_t         *wcsc;
95 #endif
96     double           *cycles_sum;
97 } gmx_wallcycle_t_t;
98
99 /* Each name should not exceed 19 printing characters
100    (ie. terminating null can be twentieth) */
101 static const char *wcn[ewcNR] =
102 {
103     "Run", "Step", "PP during PME", "Domain decomp.", "DD comm. load",
104     "DD comm. bounds", "Vsite constr.", "Send X to PME", "Neighbor search", "Launch GPU ops.",
105     "Comm. coord.", "Born radii", "Force", "Wait + Comm. F", "PME mesh",
106     "PME redist. X/F", "PME spread/gather", "PME 3D-FFT", "PME 3D-FFT Comm.", "PME solve LJ", "PME solve Elec",
107     "PME wait for PP", "Wait + Recv. PME F", "Wait GPU nonlocal", "Wait GPU local", "Wait GPU loc. est.", "NB X/F buffer ops.",
108     "Vsite spread", "COM pull force",
109     "Write traj.", "Update", "Constraints", "Comm. energies",
110     "Enforced rotation", "Add rot. forces", "Coordinate swapping", "IMD", "Test"
111 };
112
113 static const char *wcsn[ewcsNR] =
114 {
115     "DD redist.", "DD NS grid + sort", "DD setup comm.",
116     "DD make top.", "DD make constr.", "DD top. other",
117     "NS grid local", "NS grid non-loc.", "NS search local", "NS search non-loc.",
118     "Bonded F",
119     "Bonded-FEP F",
120     "Restraints F",
121     "Listed buffer ops.",
122     "Nonbonded F",
123     "Ewald F correction",
124     "NB X buffer ops.",
125     "NB F buffer ops.",
126 };
127
128 gmx_bool wallcycle_have_counter(void)
129 {
130     return gmx_cycles_have_counter();
131 }
132
133 gmx_wallcycle_t wallcycle_init(FILE *fplog, int resetstep, t_commrec gmx_unused *cr,
134                                int nthreads_pp, int nthreads_pme)
135 {
136     gmx_wallcycle_t wc;
137
138
139     if (!wallcycle_have_counter())
140     {
141         return NULL;
142     }
143
144     snew(wc, 1);
145
146     wc->wc_barrier          = FALSE;
147     wc->wcc_all             = NULL;
148     wc->wc_depth            = 0;
149     wc->ewc_prev            = -1;
150     wc->reset_counters      = resetstep;
151     wc->nthreads_pp         = nthreads_pp;
152     wc->nthreads_pme        = nthreads_pme;
153     wc->cycles_sum          = NULL;
154
155 #ifdef GMX_MPI
156     if (PAR(cr) && getenv("GMX_CYCLE_BARRIER") != NULL)
157     {
158         if (fplog)
159         {
160             fprintf(fplog, "\nWill call MPI_Barrier before each cycle start/stop call\n\n");
161         }
162         wc->wc_barrier       = TRUE;
163         wc->mpi_comm_mygroup = cr->mpi_comm_mygroup;
164     }
165 #endif
166
167     snew(wc->wcc, ewcNR);
168     if (getenv("GMX_CYCLE_ALL") != NULL)
169     {
170         if (fplog)
171         {
172             fprintf(fplog, "\nWill time all the code during the run\n\n");
173         }
174         snew(wc->wcc_all, ewcNR*ewcNR);
175     }
176
177 #ifdef GMX_CYCLE_SUBCOUNTERS
178     snew(wc->wcsc, ewcsNR);
179 #endif
180
181 #ifdef DEBUG_WCYCLE
182     wc->count_depth = 0;
183 #endif
184
185     return wc;
186 }
187
188 void wallcycle_destroy(gmx_wallcycle_t wc)
189 {
190     if (wc == NULL)
191     {
192         return;
193     }
194
195     if (wc->wcc != NULL)
196     {
197         sfree(wc->wcc);
198     }
199     if (wc->wcc_all != NULL)
200     {
201         sfree(wc->wcc_all);
202     }
203 #ifdef GMX_CYCLE_SUBCOUNTERS
204     if (wc->wcsc != NULL)
205     {
206         sfree(wc->wcsc);
207     }
208 #endif
209     sfree(wc);
210 }
211
212 static void wallcycle_all_start(gmx_wallcycle_t wc, int ewc, gmx_cycles_t cycle)
213 {
214     wc->ewc_prev   = ewc;
215     wc->cycle_prev = cycle;
216 }
217
218 static void wallcycle_all_stop(gmx_wallcycle_t wc, int ewc, gmx_cycles_t cycle)
219 {
220     wc->wcc_all[wc->ewc_prev*ewcNR+ewc].n += 1;
221     wc->wcc_all[wc->ewc_prev*ewcNR+ewc].c += cycle - wc->cycle_prev;
222 }
223
224
225 #ifdef DEBUG_WCYCLE
226 static void debug_start_check(gmx_wallcycle_t wc, int ewc)
227 {
228     /* fprintf(stderr,"wcycle_start depth %d, %s\n",wc->count_depth,wcn[ewc]); */
229
230     if (wc->count_depth < 0 || wc->count_depth >= DEPTH_MAX)
231     {
232         gmx_fatal(FARGS, "wallcycle counter depth out of range: %d",
233                   wc->count_depth);
234     }
235     wc->counterlist[wc->count_depth] = ewc;
236     wc->count_depth++;
237 }
238
239 static void debug_stop_check(gmx_wallcycle_t wc, int ewc)
240 {
241     wc->count_depth--;
242
243     /* fprintf(stderr,"wcycle_stop depth %d, %s\n",wc->count_depth,wcn[ewc]); */
244
245     if (wc->count_depth < 0)
246     {
247         gmx_fatal(FARGS, "wallcycle counter depth out of range when stopping %s: %d", wcn[ewc], wc->count_depth);
248     }
249     if (wc->counterlist[wc->count_depth] != ewc)
250     {
251         gmx_fatal(FARGS, "wallcycle mismatch at stop, start %s, stop %s",
252                   wcn[wc->counterlist[wc->count_depth]], wcn[ewc]);
253     }
254 }
255 #endif
256
257 void wallcycle_start(gmx_wallcycle_t wc, int ewc)
258 {
259     gmx_cycles_t cycle;
260
261     if (wc == NULL)
262     {
263         return;
264     }
265
266 #ifdef GMX_MPI
267     if (wc->wc_barrier)
268     {
269         MPI_Barrier(wc->mpi_comm_mygroup);
270     }
271 #endif
272
273 #ifdef DEBUG_WCYCLE
274     debug_start_check(wc, ewc);
275 #endif
276
277     cycle              = gmx_cycles_read();
278     wc->wcc[ewc].start = cycle;
279     if (wc->wcc_all != NULL)
280     {
281         wc->wc_depth++;
282         if (ewc == ewcRUN)
283         {
284             wallcycle_all_start(wc, ewc, cycle);
285         }
286         else if (wc->wc_depth == 3)
287         {
288             wallcycle_all_stop(wc, ewc, cycle);
289         }
290     }
291 }
292
293 void wallcycle_start_nocount(gmx_wallcycle_t wc, int ewc)
294 {
295     if (wc == NULL)
296     {
297         return;
298     }
299
300     wallcycle_start(wc, ewc);
301     wc->wcc[ewc].n--;
302 }
303
304 double wallcycle_stop(gmx_wallcycle_t wc, int ewc)
305 {
306     gmx_cycles_t cycle, last;
307
308     if (wc == NULL)
309     {
310         return 0;
311     }
312
313 #ifdef GMX_MPI
314     if (wc->wc_barrier)
315     {
316         MPI_Barrier(wc->mpi_comm_mygroup);
317     }
318 #endif
319
320 #ifdef DEBUG_WCYCLE
321     debug_stop_check(wc, ewc);
322 #endif
323
324     cycle           = gmx_cycles_read();
325     last            = cycle - wc->wcc[ewc].start;
326     wc->wcc[ewc].c += last;
327     wc->wcc[ewc].n++;
328     if (wc->wcc_all)
329     {
330         wc->wc_depth--;
331         if (ewc == ewcRUN)
332         {
333             wallcycle_all_stop(wc, ewc, cycle);
334         }
335         else if (wc->wc_depth == 2)
336         {
337             wallcycle_all_start(wc, ewc, cycle);
338         }
339     }
340
341     return last;
342 }
343
344 void wallcycle_get(gmx_wallcycle_t wc, int ewc, int *n, double *c)
345 {
346     *n = wc->wcc[ewc].n;
347     *c = (double)wc->wcc[ewc].c;
348 }
349
350 void wallcycle_reset_all(gmx_wallcycle_t wc)
351 {
352     int i;
353
354     if (wc == NULL)
355     {
356         return;
357     }
358
359     for (i = 0; i < ewcNR; i++)
360     {
361         wc->wcc[i].n = 0;
362         wc->wcc[i].c = 0;
363     }
364     if (wc->wcc_all)
365     {
366         for (i = 0; i < ewcNR*ewcNR; i++)
367         {
368             wc->wcc_all[i].n = 0;
369             wc->wcc_all[i].c = 0;
370         }
371     }
372 #ifdef GMX_CYCLE_SUBCOUNTERS
373     for (i = 0; i < ewcsNR; i++)
374     {
375         wc->wcsc[i].n = 0;
376         wc->wcsc[i].c = 0;
377     }
378 #endif
379 }
380
381 static gmx_bool is_pme_counter(int ewc)
382 {
383     return (ewc >= ewcPMEMESH && ewc <= ewcPMEWAITCOMM);
384 }
385
386 static gmx_bool is_pme_subcounter(int ewc)
387 {
388     return (ewc >= ewcPME_REDISTXF && ewc < ewcPMEWAITCOMM);
389 }
390
391 void wallcycle_sum(t_commrec *cr, gmx_wallcycle_t wc)
392 {
393     wallcc_t *wcc;
394     double    cycles[ewcNR+ewcsNR];
395     double    cycles_n[ewcNR+ewcsNR], buf[ewcNR+ewcsNR], *cyc_all, *buf_all;
396     int       i, j;
397     int       nsum;
398
399     if (wc == NULL)
400     {
401         return;
402     }
403
404     snew(wc->cycles_sum, ewcNR+ewcsNR);
405
406     wcc = wc->wcc;
407
408     /* The GPU wait estimate counter is used for load balancing only
409      * and will mess up the total due to double counting: clear it.
410      */
411     wcc[ewcWAIT_GPU_NB_L_EST].n = 0;
412     wcc[ewcWAIT_GPU_NB_L_EST].c = 0;
413
414     for (i = 0; i < ewcNR; i++)
415     {
416         if (is_pme_counter(i) || (i == ewcRUN && cr->duty == DUTY_PME))
417         {
418             wcc[i].c *= wc->nthreads_pme;
419
420             if (wc->wcc_all)
421             {
422                 for (j = 0; j < ewcNR; j++)
423                 {
424                     wc->wcc_all[i*ewcNR+j].c *= wc->nthreads_pme;
425                 }
426             }
427         }
428         else
429         {
430             wcc[i].c *= wc->nthreads_pp;
431
432             if (wc->wcc_all)
433             {
434                 for (j = 0; j < ewcNR; j++)
435                 {
436                     wc->wcc_all[i*ewcNR+j].c *= wc->nthreads_pp;
437                 }
438             }
439         }
440     }
441
442     if (wcc[ewcDDCOMMLOAD].n > 0)
443     {
444         wcc[ewcDOMDEC].c -= wcc[ewcDDCOMMLOAD].c;
445     }
446     if (wcc[ewcDDCOMMBOUND].n > 0)
447     {
448         wcc[ewcDOMDEC].c -= wcc[ewcDDCOMMBOUND].c;
449     }
450     if (wcc[ewcPME_FFTCOMM].n > 0)
451     {
452         wcc[ewcPME_FFT].c -= wcc[ewcPME_FFTCOMM].c;
453     }
454
455     if (cr->npmenodes == 0)
456     {
457         /* All nodes do PME (or no PME at all) */
458         if (wcc[ewcPMEMESH].n > 0)
459         {
460             wcc[ewcFORCE].c -= wcc[ewcPMEMESH].c;
461         }
462     }
463     else
464     {
465         /* The are PME-only nodes */
466         if (wcc[ewcPMEMESH].n > 0)
467         {
468             /* This must be a PME only node, calculate the Wait + Comm. time */
469             wcc[ewcPMEWAITCOMM].c = wcc[ewcRUN].c - wcc[ewcPMEMESH].c;
470         }
471     }
472
473     /* Store the cycles in a double buffer for summing */
474     for (i = 0; i < ewcNR; i++)
475     {
476         cycles_n[i] = (double)wcc[i].n;
477         cycles[i]   = (double)wcc[i].c;
478     }
479     nsum = ewcNR;
480 #ifdef GMX_CYCLE_SUBCOUNTERS
481     for (i = 0; i < ewcsNR; i++)
482     {
483         wc->wcsc[i].c    *= wc->nthreads_pp;
484         cycles_n[ewcNR+i] = (double)wc->wcsc[i].n;
485         cycles[ewcNR+i]   = (double)wc->wcsc[i].c;
486     }
487     nsum += ewcsNR;
488 #endif
489
490 #ifdef GMX_MPI
491     if (cr->nnodes > 1)
492     {
493         MPI_Allreduce(cycles_n, buf, nsum, MPI_DOUBLE, MPI_MAX,
494                       cr->mpi_comm_mysim);
495         for (i = 0; i < ewcNR; i++)
496         {
497             wcc[i].n = (int)(buf[i] + 0.5);
498         }
499 #ifdef GMX_CYCLE_SUBCOUNTERS
500         for (i = 0; i < ewcsNR; i++)
501         {
502             wc->wcsc[i].n = (int)(buf[ewcNR+i] + 0.5);
503         }
504 #endif
505
506         MPI_Allreduce(cycles, wc->cycles_sum, nsum, MPI_DOUBLE, MPI_SUM,
507                       cr->mpi_comm_mysim);
508
509         if (wc->wcc_all != NULL)
510         {
511             snew(cyc_all, ewcNR*ewcNR);
512             snew(buf_all, ewcNR*ewcNR);
513             for (i = 0; i < ewcNR*ewcNR; i++)
514             {
515                 cyc_all[i] = wc->wcc_all[i].c;
516             }
517             MPI_Allreduce(cyc_all, buf_all, ewcNR*ewcNR, MPI_DOUBLE, MPI_SUM,
518                           cr->mpi_comm_mysim);
519             for (i = 0; i < ewcNR*ewcNR; i++)
520             {
521                 wc->wcc_all[i].c = buf_all[i];
522             }
523             sfree(buf_all);
524             sfree(cyc_all);
525         }
526     }
527     else
528 #endif
529     {
530         for (i = 0; i < nsum; i++)
531         {
532             wc->cycles_sum[i] = cycles[i];
533         }
534     }
535 }
536
537 static void print_cycles(FILE *fplog, double c2t, const char *name,
538                          int nnodes, int nthreads,
539                          int ncalls, double c_sum, double tot)
540 {
541     char   nnodes_str[6];
542     char   nthreads_str[6];
543     char   ncalls_str[11];
544     double wallt;
545     double percentage = (tot > 0.) ? (100. * c_sum / tot) : 0.;
546
547     if (c_sum > 0)
548     {
549         if (ncalls > 0)
550         {
551             snprintf(ncalls_str, sizeof(ncalls_str), "%10d", ncalls);
552             if (nnodes < 0)
553             {
554                 snprintf(nnodes_str, sizeof(nnodes_str), "N/A");
555             }
556             else
557             {
558                 snprintf(nnodes_str, sizeof(nnodes_str), "%4d", nnodes);
559             }
560             if (nthreads < 0)
561             {
562                 snprintf(nthreads_str, sizeof(nthreads_str), "N/A");
563             }
564             else
565             {
566                 snprintf(nthreads_str, sizeof(nthreads_str), "%4d", nthreads);
567             }
568         }
569         else
570         {
571             nnodes_str[0]   = 0;
572             nthreads_str[0] = 0;
573             ncalls_str[0]   = 0;
574         }
575         /* Convert the cycle count to wallclock time for this task */
576         wallt = c_sum*c2t;
577
578         fprintf(fplog, " %-19.19s %4s %4s %10s  %10.3f %14.3f %5.1f\n",
579                 name, nnodes_str, nthreads_str, ncalls_str, wallt,
580                 c_sum*1e-9, percentage);
581     }
582 }
583
584 static void print_gputimes(FILE *fplog, const char *name,
585                            int n, double t, double tot_t)
586 {
587     char num[11];
588     char avg_perf[11];
589
590     if (n > 0)
591     {
592         snprintf(num, sizeof(num), "%10d", n);
593         snprintf(avg_perf, sizeof(avg_perf), "%10.3f", t/n);
594     }
595     else
596     {
597         sprintf(num, "          ");
598         sprintf(avg_perf, "          ");
599     }
600     if (t != tot_t && tot_t > 0)
601     {
602         fprintf(fplog, " %-29s %10s%12.3f   %s   %5.1f\n",
603                 name, num, t/1000, avg_perf, 100 * t/tot_t);
604     }
605     else
606     {
607         fprintf(fplog, " %-29s %10s%12.3f   %s   %5.1f\n",
608                 name, "", t/1000, avg_perf, 100.0);
609     }
610 }
611
612 static void print_header(FILE *fplog, int nrank_pp, int nth_pp, int nrank_pme, int nth_pme)
613 {
614     int nrank_tot = nrank_pp + nrank_pme;
615     if (0 == nrank_pme)
616     {
617         fprintf(fplog, "On %d MPI rank%s", nrank_tot, nrank_tot == 1 ? "" : "s");
618         if (nth_pp > 1)
619         {
620             fprintf(fplog, ", each using %d OpenMP threads", nth_pp);
621         }
622         /* Don't report doing PP+PME, because we can't tell here if
623          * this is RF, etc. */
624     }
625     else
626     {
627         fprintf(fplog, "On %d MPI rank%s doing PP", nrank_pp, nrank_pp == 1 ? "" : "s");
628         if (nth_pp > 1)
629         {
630             fprintf(fplog, ",%s using %d OpenMP threads", nrank_pp > 1 ? " each" : "", nth_pp);
631         }
632         fprintf(fplog, ", and\non %d MPI rank%s doing PME", nrank_pme, nrank_pme == 1 ? "" : "s");
633         if (nth_pme > 1)
634         {
635             fprintf(fplog, ",%s using %d OpenMP threads", nrank_pme > 1 ? " each" : "", nth_pme);
636         }
637     }
638
639     fprintf(fplog, "\n\n");
640     fprintf(fplog, " Computing:          Num   Num      Call    Wall time         Giga-Cycles\n");
641     fprintf(fplog, "                     Ranks Threads  Count      (s)         total sum    %%\n");
642 }
643
644 void wallcycle_print(FILE *fplog, int nnodes, int npme, double realtime,
645                      gmx_wallcycle_t wc, struct gmx_wallclock_gpu_t *gpu_t)
646 {
647     double     *cyc_sum;
648     double      tot, tot_for_pp, tot_for_rest, tot_gpu, tot_cpu_overlap, gpu_cpu_ratio, tot_k;
649     double      c2t, c2t_pp, c2t_pme = 0;
650     int         i, j, npp, nth_pp, nth_pme, nth_tot;
651     char        buf[STRLEN];
652     const char *hline = "-----------------------------------------------------------------------------";
653
654     if (wc == NULL)
655     {
656         return;
657     }
658
659     nth_pp  = wc->nthreads_pp;
660     assert(nth_pp > 0);
661
662     nth_pme = wc->nthreads_pme;
663     assert(nth_pme > 0);
664
665     cyc_sum = wc->cycles_sum;
666
667     assert(nnodes > 0);
668     assert(npme >= 0);
669     npp     = nnodes - npme;
670     /* npme is the number of PME-only ranks used, and we always do PP work */
671     assert(npp > 0);
672
673     nth_tot = npp*nth_pp + npme*nth_pme;
674
675     /* When using PME-only nodes, the next line is valid for both
676        PP-only and PME-only nodes because they started ewcRUN at the
677        same time. */
678     tot        = cyc_sum[ewcRUN];
679     tot_for_pp = 0;
680
681     if (tot <= 0.0)
682     {
683         /* TODO This is heavy handed, but until someone reworks the
684            code so that it is provably robust with respect to
685            non-positive values for all possible timer and cycle
686            counters, there is less value gained from printing whatever
687            timing data might still be sensible for some non-Jenkins
688            run, than is lost from diagnosing Jenkins FP exceptions on
689            runs about whose execution time we don't care. */
690         md_print_warn(NULL, fplog, "WARNING: A total of %f CPU cycles was recorded, so mdrun cannot print a time accounting\n", tot);
691         return;
692     }
693
694     /* Conversion factor from cycles to seconds */
695     c2t     = realtime/tot;
696     c2t_pp  = c2t * nth_tot / (double) (npp*nth_pp);
697     if (npme > 0)
698     {
699         c2t_pme = c2t * nth_tot / (double) (npme*nth_pme);
700     }
701     else
702     {
703         c2t_pme = 0;
704     }
705
706     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");
707
708     print_header(fplog, npp, nth_pp, npme, nth_pme);
709
710     fprintf(fplog, "%s\n", hline);
711     for (i = ewcPPDURINGPME+1; i < ewcNR; i++)
712     {
713         if (is_pme_subcounter(i))
714         {
715             /* Do not count these at all */
716         }
717         else if (npme > 0 && is_pme_counter(i))
718         {
719             /* Print timing information for PME-only nodes, but add an
720              * asterisk so the reader of the table can know that the
721              * walltimes are not meant to add up. The asterisk still
722              * fits in the required maximum of 19 characters. */
723             char buffer[STRLEN];
724             snprintf(buffer, STRLEN, "%s *", wcn[i]);
725             print_cycles(fplog, c2t_pme, buffer,
726                          npme, nth_pme,
727                          wc->wcc[i].n, cyc_sum[i], tot);
728         }
729         else
730         {
731             /* Print timing information when it is for a PP or PP+PME
732                node */
733             print_cycles(fplog, c2t_pp, wcn[i],
734                          npp, nth_pp,
735                          wc->wcc[i].n, cyc_sum[i], tot);
736             tot_for_pp += cyc_sum[i];
737         }
738     }
739     if (wc->wcc_all != NULL)
740     {
741         for (i = 0; i < ewcNR; i++)
742         {
743             for (j = 0; j < ewcNR; j++)
744             {
745                 snprintf(buf, 20, "%-9.9s %-9.9s", wcn[i], wcn[j]);
746                 print_cycles(fplog, c2t_pp, buf,
747                              npp, nth_pp,
748                              wc->wcc_all[i*ewcNR+j].n,
749                              wc->wcc_all[i*ewcNR+j].c,
750                              tot);
751             }
752         }
753     }
754     tot_for_rest = tot * (npp * nth_pp) / (double) nth_tot;
755     print_cycles(fplog, c2t_pp, "Rest",
756                  npp, nth_pp,
757                  -1, tot_for_rest - tot_for_pp, tot);
758     fprintf(fplog, "%s\n", hline);
759     print_cycles(fplog, c2t, "Total",
760                  npp, nth_pp,
761                  -1, tot, tot);
762     fprintf(fplog, "%s\n", hline);
763
764     if (npme > 0)
765     {
766         fprintf(fplog,
767                 "(*) Note that with separate PME ranks, the walltime column actually sums to\n"
768                 "    twice the total reported, but the cycle count total and %% are correct.\n"
769                 "%s\n", hline);
770     }
771
772     if (wc->wcc[ewcPMEMESH].n > 0)
773     {
774         fprintf(fplog, " Breakdown of PME mesh computation\n");
775         fprintf(fplog, "%s\n", hline);
776         for (i = ewcPPDURINGPME+1; i < ewcNR; i++)
777         {
778             if (is_pme_subcounter(i))
779             {
780                 print_cycles(fplog, npme > 0 ? c2t_pme : c2t_pp, wcn[i],
781                              npme > 0 ? npme : npp, nth_pme,
782                              wc->wcc[i].n, cyc_sum[i], tot);
783             }
784         }
785         fprintf(fplog, "%s\n", hline);
786     }
787
788 #ifdef GMX_CYCLE_SUBCOUNTERS
789     fprintf(fplog, " Breakdown of PP computation\n");
790     fprintf(fplog, "%s\n", hline);
791     for (i = 0; i < ewcsNR; i++)
792     {
793         print_cycles(fplog, c2t_pp, wcsn[i],
794                      npp, nth_pp,
795                      wc->wcsc[i].n, cyc_sum[ewcNR+i], tot);
796     }
797     fprintf(fplog, "%s\n", hline);
798 #endif
799
800     /* print GPU timing summary */
801     if (gpu_t)
802     {
803         const char *k_log_str[2][2] = {
804             {"Nonbonded F kernel", "Nonbonded F+ene k."},
805             {"Nonbonded F+prune k.", "Nonbonded F+ene+prune k."}
806         };
807
808         tot_gpu = gpu_t->pl_h2d_t + gpu_t->nb_h2d_t + gpu_t->nb_d2h_t;
809
810         /* add up the kernel timings */
811         tot_k = 0.0;
812         for (i = 0; i < 2; i++)
813         {
814             for (j = 0; j < 2; j++)
815             {
816                 tot_k += gpu_t->ktime[i][j].t;
817             }
818         }
819         tot_gpu += tot_k;
820
821         tot_cpu_overlap = wc->wcc[ewcFORCE].c;
822         if (wc->wcc[ewcPMEMESH].n > 0)
823         {
824             tot_cpu_overlap += wc->wcc[ewcPMEMESH].c;
825         }
826         tot_cpu_overlap *= realtime*1000/tot; /* convert s to ms */
827
828         fprintf(fplog, "\n GPU timings\n%s\n", hline);
829         fprintf(fplog, " Computing:                         Count  Wall t (s)      ms/step       %c\n", '%');
830         fprintf(fplog, "%s\n", hline);
831         print_gputimes(fplog, "Pair list H2D",
832                        gpu_t->pl_h2d_c, gpu_t->pl_h2d_t, tot_gpu);
833         print_gputimes(fplog, "X / q H2D",
834                        gpu_t->nb_c, gpu_t->nb_h2d_t, tot_gpu);
835
836         for (i = 0; i < 2; i++)
837         {
838             for (j = 0; j < 2; j++)
839             {
840                 if (gpu_t->ktime[i][j].c)
841                 {
842                     print_gputimes(fplog, k_log_str[i][j],
843                                    gpu_t->ktime[i][j].c, gpu_t->ktime[i][j].t, tot_gpu);
844                 }
845             }
846         }
847
848         print_gputimes(fplog, "F D2H",  gpu_t->nb_c, gpu_t->nb_d2h_t, tot_gpu);
849         fprintf(fplog, "%s\n", hline);
850         print_gputimes(fplog, "Total ", gpu_t->nb_c, tot_gpu, tot_gpu);
851         fprintf(fplog, "%s\n", hline);
852
853         gpu_cpu_ratio = tot_gpu/tot_cpu_overlap;
854         if (gpu_t->nb_c > 0 && wc->wcc[ewcFORCE].n > 0)
855         {
856             fprintf(fplog, "\nForce evaluation time GPU/CPU: %.3f ms/%.3f ms = %.3f\n",
857                     tot_gpu/gpu_t->nb_c, tot_cpu_overlap/wc->wcc[ewcFORCE].n,
858                     gpu_cpu_ratio);
859         }
860
861         /* only print notes related to CPU-GPU load balance with PME */
862         if (wc->wcc[ewcPMEMESH].n > 0)
863         {
864             fprintf(fplog, "For optimal performance this ratio should be close to 1!\n");
865
866             /* print note if the imbalance is high with PME case in which
867              * CPU-GPU load balancing is possible */
868             if (gpu_cpu_ratio < 0.75 || gpu_cpu_ratio > 1.2)
869             {
870                 /* Only the sim master calls this function, so always print to stderr */
871                 if (gpu_cpu_ratio < 0.75)
872                 {
873                     if (npp > 1)
874                     {
875                         /* The user could have used -notunepme,
876                          * but we currently can't check that here.
877                          */
878                         md_print_warn(NULL, fplog,
879                                       "\nNOTE: The GPU has >25%% less load than the CPU. This imbalance causes\n"
880                                       "      performance loss. Maybe the domain decomposition limits the PME tuning.\n"
881                                       "      In that case, try setting the DD grid manually (-dd) or lowering -dds.");
882                     }
883                     else
884                     {
885                         /* We should not end up here, unless the box is
886                          * too small for increasing the cut-off for PME tuning.
887                          */
888                         md_print_warn(NULL, fplog,
889                                       "\nNOTE: The GPU has >25%% less load than the CPU. This imbalance causes\n"
890                                       "      performance loss.");
891                     }
892                 }
893                 if (gpu_cpu_ratio > 1.2)
894                 {
895                     md_print_warn(NULL, fplog,
896                                   "\nNOTE: The GPU has >20%% more load than the CPU. This imbalance causes\n"
897                                   "      performance loss, consider using a shorter cut-off and a finer PME grid.");
898                 }
899             }
900         }
901     }
902
903     if (wc->wc_barrier)
904     {
905         md_print_warn(NULL, fplog,
906                       "MPI_Barrier was called before each cycle start/stop\n"
907                       "call, so timings are not those of real runs.\n");
908     }
909
910     if (wc->wcc[ewcNB_XF_BUF_OPS].n > 0 &&
911         (cyc_sum[ewcDOMDEC] > tot*0.1 ||
912          cyc_sum[ewcNS] > tot*0.1))
913     {
914         /* Only the sim master calls this function, so always print to stderr */
915         if (wc->wcc[ewcDOMDEC].n == 0)
916         {
917             md_print_warn(NULL, fplog,
918                           "NOTE: %d %% of the run time was spent in pair search,\n"
919                           "      you might want to increase nstlist (this has no effect on accuracy)\n",
920                           (int)(100*cyc_sum[ewcNS]/tot+0.5));
921         }
922         else
923         {
924             md_print_warn(NULL, fplog,
925                           "NOTE: %d %% of the run time was spent in domain decomposition,\n"
926                           "      %d %% of the run time was spent in pair search,\n"
927                           "      you might want to increase nstlist (this has no effect on accuracy)\n",
928                           (int)(100*cyc_sum[ewcDOMDEC]/tot+0.5),
929                           (int)(100*cyc_sum[ewcNS]/tot+0.5));
930         }
931     }
932
933     if (cyc_sum[ewcMoveE] > tot*0.05)
934     {
935         /* Only the sim master calls this function, so always print to stderr */
936         md_print_warn(NULL, fplog,
937                       "NOTE: %d %% of the run time was spent communicating energies,\n"
938                       "      you might want to use the -gcom option of mdrun\n",
939                       (int)(100*cyc_sum[ewcMoveE]/tot+0.5));
940     }
941 }
942
943 extern gmx_int64_t wcycle_get_reset_counters(gmx_wallcycle_t wc)
944 {
945     if (wc == NULL)
946     {
947         return -1;
948     }
949
950     return wc->reset_counters;
951 }
952
953 extern void wcycle_set_reset_counters(gmx_wallcycle_t wc, gmx_int64_t reset_counters)
954 {
955     if (wc == NULL)
956     {
957         return;
958     }
959
960     wc->reset_counters = reset_counters;
961 }
962
963 #ifdef GMX_CYCLE_SUBCOUNTERS
964
965 void wallcycle_sub_start(gmx_wallcycle_t wc, int ewcs)
966 {
967     if (wc != NULL)
968     {
969         wc->wcsc[ewcs].start = gmx_cycles_read();
970     }
971 }
972
973 void wallcycle_sub_start_nocount(gmx_wallcycle_t wc, int ewcs)
974 {
975     if (wc == NULL)
976     {
977         return;
978     }
979
980     wallcycle_sub_start(wc, ewcs);
981     wc->wcsc[ewcs].n--;
982 }
983
984 void wallcycle_sub_stop(gmx_wallcycle_t wc, int ewcs)
985 {
986     if (wc != NULL)
987     {
988         wc->wcsc[ewcs].c += gmx_cycles_read() - wc->wcsc[ewcs].start;
989         wc->wcsc[ewcs].n++;
990     }
991 }
992
993 #else
994
995 void wallcycle_sub_start(gmx_wallcycle_t gmx_unused wc, int gmx_unused ewcs)
996 {
997 }
998 void wallcycle_sub_start_nocount(gmx_wallcycle_t gmx_unused wc, int gmx_unused ewcs)
999 {
1000 }
1001 void wallcycle_sub_stop(gmx_wallcycle_t gmx_unused wc, int gmx_unused ewcs)
1002 {
1003 }
1004
1005 #endif /* GMX_CYCLE_SUBCOUNTERS */