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