4705fca79012177e714abec4071be2abc0dca29a
[alexxy/gromacs.git] / src / gromacs / mdlib / perf_est.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) 2012,2014,2015,2016,2017,2018, 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 "perf_est.h"
40
41 #include <cmath>
42
43 #include "gromacs/math/functions.h"
44 #include "gromacs/math/utilities.h"
45 #include "gromacs/math/vec.h"
46 #include "gromacs/mdlib/nbnxn_consts.h"
47 #include "gromacs/mdlib/nbnxn_search.h"
48 #include "gromacs/mdtypes/commrec.h"
49 #include "gromacs/mdtypes/inputrec.h"
50 #include "gromacs/mdtypes/md_enums.h"
51 #include "gromacs/simd/simd.h"
52 #include "gromacs/topology/ifunc.h"
53 #include "gromacs/topology/topology.h"
54 #include "gromacs/utility/fatalerror.h"
55
56 /* Computational cost of bonded, non-bonded and PME calculations.
57  * This will be machine dependent.
58  * The numbers are only used for estimating the relative cost of PME vs PP,
59  * so only relative numbers matter.
60  * The numbers here are accurate cycle counts for Haswell in single precision
61  * compiled with gcc5.2. A correction factor for other architectures is given
62  * by simd_cycle_factor().
63  * In double precision PME mesh is slightly cheaper, although not so much
64  * that the numbers need to be adjusted.
65  */
66
67 /* Cost of a pair interaction in the "group" cut-off scheme */
68 static const double c_group_fq        = 18.0;
69 static const double c_group_qlj_cut   = 18.0;
70 static const double c_group_qlj_tab   = 24.0;
71 static const double c_group_lj_cut    = 12.0;
72 static const double c_group_lj_tab    = 21.0;
73 /* Cost of 1 water with one Q/LJ atom */
74 static const double c_group_qljw_cut  = 24.0;
75 static const double c_group_qljw_tab  = 27.0;
76 /* Cost of 1 water with one Q atom or with 1/3 water (LJ negligible) */
77 static const double c_group_qw        = 21.0;
78
79 /* Cost of a pair interaction in the "Verlet" cut-off scheme, QEXP is Ewald */
80 static const double c_nbnxn_lj        =  2.5;
81 static const double c_nbnxn_qrf_lj    =  2.9;
82 static const double c_nbnxn_qrf       =  2.4;
83 static const double c_nbnxn_qexp_lj   =  4.2;
84 static const double c_nbnxn_qexp      =  3.8;
85 /* Extra cost for expensive LJ interaction, e.g. pot-switch or LJ-PME */
86 static const double c_nbnxn_ljexp_add =  1.0;
87
88 /* Cost of the different components of PME. */
89 /* Cost of particle reordering and redistribution (no SIMD correction).
90  * This will be zero without MPI and can be very high with load imbalance.
91  * Thus we use an approximate value for medium parallelization.
92  */
93 static const double c_pme_redist = 100.0;
94 /* Cost of q spreading and force interpolation per charge. This part almost
95  * doesn't accelerate with SIMD, so we don't use SIMD correction.
96  */
97 static const double c_pme_spread =   5.0;
98 /* Cost of fft's, will be multiplied with 2 N log2(N) (no SIMD correction)
99  * Without MPI the number is 2-3, depending on grid factors and thread count.
100  * We take the high limit to be on the safe side and account for some MPI
101  * communication cost, which will dominate at high parallelization.
102  */
103 static const double c_pme_fft    =   3.0;
104 /* Cost of pme_solve, will be multiplied with N */
105 static const double c_pme_solve  =   9.0;
106
107 /* Cost of a bonded interaction divided by the number of distances calculations
108  * required in one interaction. The actual cost is nearly propotional to this.
109  */
110 static const double c_bond       =  25.0;
111
112
113 #if GMX_SIMD_HAVE_REAL
114 static const gmx_bool bHaveSIMD = TRUE;
115 #else
116 static const gmx_bool bHaveSIMD = FALSE;
117 #endif
118
119 /* Gives a correction factor for the currently compiled SIMD implementations
120  * versus the reference used for the coefficients above (8-wide SIMD with FMA).
121  * bUseSIMD sets if we asking for plain-C (FALSE) or SIMD (TRUE) code.
122  */
123 static double simd_cycle_factor(gmx_bool bUseSIMD)
124 {
125     /* The (average) ratio of the time taken by plain-C force calculations
126      * relative to SIMD versions, for the reference platform Haswell:
127      * 8-wide SIMD with FMA, factor: sqrt(2*8)*1.25 = 5.
128      * This factor is used for normalization in simd_cycle_factor().
129      */
130     const double simd_cycle_no_simd = 5.0;
131     double       speedup;
132
133 #if GMX_SIMD_HAVE_REAL
134     if (bUseSIMD)
135     {
136         /* We never get full speed-up of a factor GMX_SIMD_REAL_WIDTH.
137          * The actual speed-up depends very much on gather+scatter overhead,
138          * which is different for different bonded and non-bonded kernels.
139          * As a rough, but actually not bad, approximation we use a sqrt
140          * dependence on the width which gives a factor 4 for width=8.
141          */
142         speedup = std::sqrt(2.0*GMX_SIMD_REAL_WIDTH);
143 #if GMX_SIMD_HAVE_FMA
144         /* FMA tends to give a bit more speedup */
145         speedup *= 1.25;
146 #endif
147     }
148     else
149     {
150         speedup  = 1.0;
151     }
152 #else
153     if (bUseSIMD)
154     {
155         gmx_incons("gmx_cycle_factor() compiled without SIMD called with bUseSIMD=TRUE");
156     }
157     /* No SIMD, no speedup */
158     speedup      = 1.0;
159 #endif
160
161     /* Return speed compared to the reference (Haswell).
162      * For x86 SIMD, the nbnxn kernels are relatively much slower on
163      * Sandy/Ivy Bridge than Haswell, but that only leads to a too high
164      * PME load estimate on SB/IB, which is erring on the safe side.
165      */
166     return simd_cycle_no_simd/speedup;
167 }
168
169 void count_bonded_distances(const gmx_mtop_t *mtop, const t_inputrec *ir,
170                             double *ndistance_c, double *ndistance_simd)
171 {
172     gmx_bool       bExcl;
173     double         nonsimd_step_frac;
174     int            ftype;
175     double         ndtot_c, ndtot_simd;
176 #if GMX_SIMD_HAVE_REAL
177     gmx_bool       bSimdBondeds = TRUE;
178 #else
179     gmx_bool       bSimdBondeds = FALSE;
180 #endif
181
182     bExcl = (ir->cutoff_scheme == ecutsGROUP && inputrecExclForces(ir)
183              && !EEL_FULL(ir->coulombtype));
184
185     if (bSimdBondeds)
186     {
187         /* We only have SIMD versions of these bondeds without energy and
188          * without shift-forces, we take that into account here.
189          */
190         if (ir->nstcalcenergy > 0)
191         {
192             nonsimd_step_frac = 1.0/ir->nstcalcenergy;
193         }
194         else
195         {
196             nonsimd_step_frac = 0;
197         }
198         if (ir->epc != epcNO && 1.0/ir->nstpcouple > nonsimd_step_frac)
199         {
200             nonsimd_step_frac = 1.0/ir->nstpcouple;
201         }
202     }
203     else
204     {
205         nonsimd_step_frac = 1;
206     }
207
208     /* Count the number of pbc_rvec_sub calls required for bonded interactions.
209      * This number is also roughly proportional to the computational cost.
210      */
211     ndtot_c    = 0;
212     ndtot_simd = 0;
213     for (const gmx_molblock_t &molb : mtop->molblock)
214     {
215         const gmx_moltype_t *molt = &mtop->moltype[molb.type];
216         for (ftype = 0; ftype < F_NRE; ftype++)
217         {
218             int nbonds;
219
220             if (interaction_function[ftype].flags & IF_BOND)
221             {
222                 double nd_c, nd_simd;
223
224                 nd_c    = 0;
225                 nd_simd = 0;
226                 /* For all interactions, except for the three exceptions
227                  * in the switch below, #distances = #atoms - 1.
228                  */
229                 switch (ftype)
230                 {
231                     case F_POSRES:
232                     case F_FBPOSRES:
233                         nd_c    = 1;
234                         break;
235                     case F_CONNBONDS:
236                         break;
237                     /* These bonded potentially use SIMD */
238                     case F_ANGLES:
239                     case F_PDIHS:
240                     case F_RBDIHS:
241                     case F_LJ14:
242                         nd_c    =      nonsimd_step_frac *(NRAL(ftype) - 1);
243                         nd_simd = (1 - nonsimd_step_frac)*(NRAL(ftype) - 1);
244                         break;
245                     default:
246                         nd_c    = NRAL(ftype) - 1;
247                         break;
248                 }
249                 nbonds      = molb.nmol*molt->ilist[ftype].size()/(1 + NRAL(ftype));
250                 ndtot_c    += nbonds*nd_c;
251                 ndtot_simd += nbonds*nd_simd;
252             }
253         }
254         if (bExcl)
255         {
256             ndtot_c += molb.nmol*(molt->excls.nra - molt->atoms.nr)/2.;
257         }
258     }
259
260     if (debug)
261     {
262         fprintf(debug, "nr. of distance calculations in bondeds: C %.1f SIMD %.1f\n", ndtot_c, ndtot_simd);
263     }
264
265     if (ndistance_c    != nullptr)
266     {
267         *ndistance_c    = ndtot_c;
268     }
269     if (ndistance_simd != nullptr)
270     {
271         *ndistance_simd = ndtot_simd;
272     }
273 }
274
275 static void pp_group_load(const gmx_mtop_t *mtop, const t_inputrec *ir,
276                           const matrix box,
277                           int *nq_tot, int *nlj_tot,
278                           double *cost_pp,
279                           gmx_bool *bChargePerturbed, gmx_bool *bTypePerturbed)
280 {
281     int            atnr, cg, a0, ncqlj, ncq, nclj;
282     gmx_bool       bBHAM, bLJcut, bWater, bQ, bLJ;
283     int            nw, nqlj, nq, nlj;
284     double         fq, fqlj, flj, fqljw, fqw;
285     t_iparams     *iparams;
286
287     bBHAM = (mtop->ffparams.functype[0] == F_BHAM);
288
289     bLJcut = ((ir->vdwtype == evdwCUT) && !bBHAM);
290
291     /* Computational cost of bonded, non-bonded and PME calculations.
292      * This will be machine dependent.
293      * The numbers here are accurate for Intel Core2 and AMD Athlon 64
294      * in single precision. In double precision PME mesh is slightly cheaper,
295      * although not so much that the numbers need to be adjusted.
296      */
297     fq    = c_group_fq;
298     fqlj  = (bLJcut ? c_group_qlj_cut : c_group_qlj_tab);
299     flj   = (bLJcut ? c_group_lj_cut  : c_group_lj_tab);
300     /* Cost of 1 water with one Q/LJ atom */
301     fqljw = (bLJcut ? c_group_qljw_cut : c_group_qljw_tab);
302     /* Cost of 1 water with one Q atom or with 1/3 water (LJ negligible) */
303     fqw   = c_group_qw;
304
305     iparams           = mtop->ffparams.iparams;
306     atnr              = mtop->ffparams.atnr;
307     nw                = 0;
308     nqlj              = 0;
309     nq                = 0;
310     nlj               = 0;
311     *bChargePerturbed = FALSE;
312     *bTypePerturbed   = FALSE;
313     for (const gmx_molblock_t &molb : mtop->molblock)
314     {
315         const gmx_moltype_t *molt = &mtop->moltype[molb.type];
316         const t_atom        *atom = molt->atoms.atom;
317         int                  a    = 0;
318         for (cg = 0; cg < molt->cgs.nr; cg++)
319         {
320             bWater = !bBHAM;
321             ncqlj  = 0;
322             ncq    = 0;
323             nclj   = 0;
324             a0     = a;
325             while (a < molt->cgs.index[cg+1])
326             {
327                 bQ  = (atom[a].q != 0 || atom[a].qB != 0);
328                 bLJ = (iparams[(atnr+1)*atom[a].type].lj.c6  != 0 ||
329                        iparams[(atnr+1)*atom[a].type].lj.c12 != 0);
330                 if (atom[a].q != atom[a].qB)
331                 {
332                     *bChargePerturbed = TRUE;
333                 }
334                 if (atom[a].type != atom[a].typeB)
335                 {
336                     *bTypePerturbed = TRUE;
337                 }
338                 /* This if this atom fits into water optimization */
339                 if (!((a == a0   &&  bQ &&  bLJ) ||
340                       (a == a0+1 &&  bQ && !bLJ) ||
341                       (a == a0+2 &&  bQ && !bLJ && atom[a].q == atom[a-1].q) ||
342                       (a == a0+3 && !bQ &&  bLJ)))
343                 {
344                     bWater = FALSE;
345                 }
346                 if (bQ && bLJ)
347                 {
348                     ncqlj++;
349                 }
350                 else
351                 {
352                     if (bQ)
353                     {
354                         ncq++;
355                     }
356                     if (bLJ)
357                     {
358                         nclj++;
359                     }
360                 }
361                 a++;
362             }
363             if (bWater)
364             {
365                 nw   += molb.nmol;
366             }
367             else
368             {
369                 nqlj += molb.nmol*ncqlj;
370                 nq   += molb.nmol*ncq;
371                 nlj  += molb.nmol*nclj;
372             }
373         }
374     }
375
376     *nq_tot  = nq  + nqlj + nw*3;
377     *nlj_tot = nlj + nqlj + nw;
378
379     if (debug)
380     {
381         fprintf(debug, "nw %d nqlj %d nq %d nlj %d\n", nw, nqlj, nq, nlj);
382     }
383
384     /* For the PP non-bonded cost it is (unrealistically) assumed
385      * that all atoms are distributed homogeneously in space.
386      * Factor 3 is used because a water molecule has 3 atoms
387      * (and TIP4P effectively has 3 interactions with (water) atoms)).
388      */
389     *cost_pp = 0.5*(fqljw*nw*nqlj +
390                     fqw  *nw*(3*nw + nq) +
391                     fqlj *nqlj*nqlj +
392                     fq   *nq*(3*nw + nqlj + nq) +
393                     flj  *nlj*(nw + nqlj + nlj))
394         *4/3*M_PI*ir->rlist*ir->rlist*ir->rlist/det(box);
395
396     *cost_pp *= simd_cycle_factor(bHaveSIMD);
397 }
398
399 static void pp_verlet_load(const gmx_mtop_t *mtop, const t_inputrec *ir,
400                            const matrix box,
401                            int *nq_tot, int *nlj_tot,
402                            double *cost_pp,
403                            gmx_bool *bChargePerturbed, gmx_bool *bTypePerturbed)
404 {
405     int            atnr, a, nqlj, nq, nlj;
406     gmx_bool       bQRF;
407     t_iparams     *iparams;
408     real           r_eff;
409     double         c_qlj, c_q, c_lj;
410     double         nppa;
411     int            j_cluster_size;
412     /* Conversion factor for reference vs SIMD kernel performance.
413      * The factor is about right for SSE2/4, but should be 2 higher for AVX256.
414      */
415 #if GMX_DOUBLE
416     const real     nbnxn_refkernel_fac = 4.0;
417 #else
418     const real     nbnxn_refkernel_fac = 8.0;
419 #endif
420
421     bQRF = (EEL_RF(ir->coulombtype) || ir->coulombtype == eelCUT);
422
423     iparams           = mtop->ffparams.iparams;
424     atnr              = mtop->ffparams.atnr;
425     nqlj              = 0;
426     nq                = 0;
427     *bChargePerturbed = FALSE;
428     *bTypePerturbed   = FALSE;
429     for (const gmx_molblock_t &molb : mtop->molblock)
430     {
431         const gmx_moltype_t *molt = &mtop->moltype[molb.type];
432         const t_atom        *atom = molt->atoms.atom;
433         for (a = 0; a < molt->atoms.nr; a++)
434         {
435             if (atom[a].q != 0 || atom[a].qB != 0)
436             {
437                 if (iparams[(atnr+1)*atom[a].type].lj.c6  != 0 ||
438                     iparams[(atnr+1)*atom[a].type].lj.c12 != 0)
439                 {
440                     nqlj += molb.nmol;
441                 }
442                 else
443                 {
444                     nq += molb.nmol;
445                 }
446             }
447             if (atom[a].q != atom[a].qB)
448             {
449                 *bChargePerturbed = TRUE;
450             }
451             if (atom[a].type != atom[a].typeB)
452             {
453                 *bTypePerturbed = TRUE;
454             }
455         }
456     }
457
458     nlj = mtop->natoms - nqlj - nq;
459
460     *nq_tot  = nqlj + nq;
461     *nlj_tot = nqlj + nlj;
462
463     /* Effective cut-off for cluster pair list of 4x4 or 4x8 atoms.
464      * This choice should match the one of pick_nbnxn_kernel_cpu().
465      * TODO: Make this function use pick_nbnxn_kernel_cpu().
466      */
467 #if GMX_SIMD_HAVE_REAL && ((GMX_SIMD_REAL_WIDTH == 8 && defined GMX_SIMD_HAVE_FMA) || GMX_SIMD_REAL_WIDTH > 8)
468     j_cluster_size = 8;
469 #else
470     j_cluster_size = 4;
471 #endif
472     r_eff = ir->rlist + nbnxn_get_rlist_effective_inc(j_cluster_size, mtop->natoms/det(box));
473
474     /* The average number of pairs per atom */
475     nppa  = 0.5*4/3*M_PI*r_eff*r_eff*r_eff*mtop->natoms/det(box);
476
477     if (debug)
478     {
479         fprintf(debug, "nqlj %d nq %d nlj %d rlist %.3f r_eff %.3f pairs per atom %.1f\n",
480                 nqlj, nq, nlj, ir->rlist, r_eff, nppa);
481     }
482
483     /* Determine the cost per pair interaction */
484     c_qlj = (bQRF ? c_nbnxn_qrf_lj : c_nbnxn_qexp_lj);
485     c_q   = (bQRF ? c_nbnxn_qrf    : c_nbnxn_qexp);
486     c_lj  = c_nbnxn_lj;
487     if (ir->vdw_modifier == eintmodPOTSWITCH || EVDW_PME(ir->vdwtype))
488     {
489         c_qlj += c_nbnxn_ljexp_add;
490         c_lj  += c_nbnxn_ljexp_add;
491     }
492     if (EVDW_PME(ir->vdwtype) && ir->ljpme_combination_rule == eljpmeLB)
493     {
494         /* We don't have LJ-PME LB comb. rule kernels, we use slow kernels */
495         c_qlj *= nbnxn_refkernel_fac;
496         c_q   *= nbnxn_refkernel_fac;
497         c_lj  *= nbnxn_refkernel_fac;
498     }
499
500     /* For the PP non-bonded cost it is (unrealistically) assumed
501      * that all atoms are distributed homogeneously in space.
502      */
503     *cost_pp = (nqlj*c_qlj + nq*c_q + nlj*c_lj)*nppa;
504
505     *cost_pp *= simd_cycle_factor(bHaveSIMD);
506 }
507
508 float pme_load_estimate(const gmx_mtop_t *mtop, const t_inputrec *ir,
509                         const matrix box)
510 {
511     int            nq_tot, nlj_tot;
512     gmx_bool       bChargePerturbed, bTypePerturbed;
513     double         ndistance_c, ndistance_simd;
514     double         cost_bond, cost_pp, cost_redist, cost_spread, cost_fft, cost_solve, cost_pme;
515     float          ratio;
516
517     /* Computational cost of bonded, non-bonded and PME calculations.
518      * This will be machine dependent.
519      * The numbers here are accurate for Intel Core2 and AMD Athlon 64
520      * in single precision. In double precision PME mesh is slightly cheaper,
521      * although not so much that the numbers need to be adjusted.
522      */
523
524     count_bonded_distances(mtop, ir, &ndistance_c, &ndistance_simd);
525     /* C_BOND is the cost for bonded interactions with SIMD implementations,
526      * so we need to scale the number of bonded interactions for which there
527      * are only C implementations to the number of SIMD equivalents.
528      */
529     cost_bond = c_bond*(ndistance_c   *simd_cycle_factor(FALSE) +
530                         ndistance_simd*simd_cycle_factor(bHaveSIMD));
531
532     if (ir->cutoff_scheme == ecutsGROUP)
533     {
534         pp_group_load(mtop, ir, box,
535                       &nq_tot, &nlj_tot, &cost_pp,
536                       &bChargePerturbed, &bTypePerturbed);
537     }
538     else
539     {
540         pp_verlet_load(mtop, ir, box,
541                        &nq_tot, &nlj_tot, &cost_pp,
542                        &bChargePerturbed, &bTypePerturbed);
543     }
544
545     cost_redist = 0;
546     cost_spread = 0;
547     cost_fft    = 0;
548     cost_solve  = 0;
549
550     int gridNkzFactor = int{
551         (ir->nkz + 1)/2
552     };
553     if (EEL_PME(ir->coulombtype))
554     {
555         double grid = ir->nkx*ir->nky*gridNkzFactor;
556
557         int    f     = ((ir->efep != efepNO && bChargePerturbed) ? 2 : 1);
558         cost_redist +=   c_pme_redist*nq_tot;
559         cost_spread += f*c_pme_spread*nq_tot*gmx::power3(ir->pme_order);
560         cost_fft    += f*c_pme_fft*grid*std::log(grid)/std::log(2.0);
561         cost_solve  += f*c_pme_solve*grid*simd_cycle_factor(bHaveSIMD);
562     }
563
564     if (EVDW_PME(ir->vdwtype))
565     {
566         double grid = ir->nkx*ir->nky*gridNkzFactor;
567
568         int    f     = ((ir->efep != efepNO && bTypePerturbed) ? 2 : 1);
569         if (ir->ljpme_combination_rule == eljpmeLB)
570         {
571             /* LB combination rule: we have 7 mesh terms */
572             f       *= 7;
573         }
574         cost_redist +=   c_pme_redist*nlj_tot;
575         cost_spread += f*c_pme_spread*nlj_tot*gmx::power3(ir->pme_order);
576         cost_fft    += f*c_pme_fft*2*grid*std::log(grid)/std::log(2.0);
577         cost_solve  += f*c_pme_solve*grid*simd_cycle_factor(bHaveSIMD);
578     }
579
580     cost_pme = cost_redist + cost_spread + cost_fft + cost_solve;
581
582     ratio = cost_pme/(cost_bond + cost_pp + cost_pme);
583
584     if (debug)
585     {
586         fprintf(debug,
587                 "cost_bond   %f\n"
588                 "cost_pp     %f\n"
589                 "cost_redist %f\n"
590                 "cost_spread %f\n"
591                 "cost_fft    %f\n"
592                 "cost_solve  %f\n",
593                 cost_bond, cost_pp, cost_redist, cost_spread, cost_fft, cost_solve);
594
595         fprintf(debug, "Estimate for relative PME load: %.3f\n", ratio);
596     }
597
598     return ratio;
599 }