f0860b164d9cbdbe61aade8bf3d5e127c5ed08cc
[alexxy/gromacs.git] / src / gromacs / gpu_utils / gpuregiontimer.h
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2016,2017,2018,2019,2020, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35
36 /*! \libinternal \file
37  *  \brief Defines the GPU region timer implementation/wrapper classes.
38  *  The implementations live in gpuregiontimer.cuh for CUDA and gpuregiontimer_ocl.h for OpenCL.
39  *
40  *  \author Aleksei Iupinov <a.yupinov@gmail.com>
41  */
42
43 #ifndef GMX_GPU_UTILS_GPUREGIONTIMER_H
44 #define GMX_GPU_UTILS_GPUREGIONTIMER_H
45
46 #include <string>
47
48 #include "gromacs/utility/gmxassert.h"
49
50 //! Debug GPU timers in debug builds only
51 #if defined(NDEBUG)
52 static const bool c_debugTimerState = false;
53 #else
54 static const bool c_debugTimerState = true;
55 #endif
56
57 /*! \libinternal \brief
58  * This is a GPU region timing wrapper class.
59  * It allows for host-side tracking of the accumulated execution timespans in GPU code
60  * (measuring kernel or transfers duration).
61  * It also partially tracks the correctness of the timer state transitions,
62  * as far as current implementation allows (see TODO in getLastRangeTime() for a disabled check).
63  * Internally it uses GpuRegionTimerImpl for measuring regions.
64  */
65 template<typename GpuRegionTimerImpl>
66 class GpuRegionTimerWrapper
67 {
68     //! The timer state used for debug-only assertions
69     enum class TimerState
70     {
71         Idle,
72         Recording,
73         Stopped
74     } debugState_ = TimerState::Idle;
75
76     //! The number of times the timespan has been measured
77     unsigned int callCount_ = 0;
78     //! The accumulated duration of the timespans measured (milliseconds)
79     double totalMilliseconds_ = 0.0;
80     //! The underlying region timer implementation
81     GpuRegionTimerImpl impl_;
82
83 public:
84     /*! \brief
85      * To be called before the region start.
86      *
87      * \param[in] deviceStream   The GPU command stream where the event being measured takes place.
88      */
89     void openTimingRegion(const DeviceStream& deviceStream)
90     {
91         if (c_debugTimerState)
92         {
93             std::string error = "GPU timer should be idle, but is "
94                                 + std::string((debugState_ == TimerState::Stopped) ? "stopped" : "recording")
95                                 + ".";
96             GMX_ASSERT(debugState_ == TimerState::Idle, error.c_str());
97             debugState_ = TimerState::Recording;
98         }
99         impl_.openTimingRegion(deviceStream);
100     }
101     /*! \brief
102      * To be called after the region end.
103      *
104      * \param[in] deviceStream   The GPU command stream where the event being measured takes place.
105      */
106     void closeTimingRegion(const DeviceStream& deviceStream)
107     {
108         if (c_debugTimerState)
109         {
110             std::string error = "GPU timer should be recording, but is "
111                                 + std::string((debugState_ == TimerState::Idle) ? "idle" : "stopped")
112                                 + ".";
113             GMX_ASSERT(debugState_ == TimerState::Recording, error.c_str());
114             debugState_ = TimerState::Stopped;
115         }
116         callCount_++;
117         impl_.closeTimingRegion(deviceStream);
118     }
119     /*! \brief
120      * Accumulates the last timespan of all the events used into the total duration,
121      * and resets the internal timer state.
122      * To be called after closeTimingRegion() and the command stream of the event having been
123      * synchronized. \returns The last timespan (in milliseconds).
124      */
125     double getLastRangeTime()
126     {
127         if (c_debugTimerState)
128         {
129             /* The assertion below is commented because it is currently triggered on a special case:
130              * the early return before the local non-bonded kernel launch if there is nothing to do.
131              * This can be reproduced in OpenCL build by running
132              * mdrun-mpi-test -ntmpi 2 --gtest_filter=*Empty*
133              * Similarly, the GpuRegionTimerImpl suffers from non-nullptr
134              * cl_event conditionals which ideally should only be asserts.
135              * TODO: improve internal task scheduling, re-enable the assert, turn conditionals into asserts
136              */
137             /*
138                std::string error = "GPU timer should be stopped, but is " + std::string((debugState_ == TimerState::Idle) ? "idle" : "recording") + ".";
139                GMX_ASSERT(debugState_ == TimerState::Stopped, error.c_str());
140              */
141             debugState_ = TimerState::Idle;
142         }
143         double milliseconds = impl_.getLastRangeTime();
144         totalMilliseconds_ += milliseconds;
145         return milliseconds;
146     }
147     /*! \brief Resets the implementation and total time/call count to zeroes. */
148     void reset()
149     {
150         if (c_debugTimerState)
151         {
152             debugState_ = TimerState::Idle;
153         }
154         totalMilliseconds_ = 0.0;
155         callCount_         = 0;
156         impl_.reset();
157     }
158     /*! \brief Gets total time recorded (in milliseconds). */
159     double getTotalTime() const { return totalMilliseconds_; }
160     /*! \brief Gets total call count recorded. */
161     unsigned int getCallCount() const { return callCount_; }
162     /*! \brief
163      * Gets a pointer to a new timing event for passing into individual GPU API calls
164      * within the region if they require it (e.g. on OpenCL).
165      * \returns The pointer to the underlying single command timing event.
166      */
167     CommandEvent* fetchNextEvent()
168     {
169         if (c_debugTimerState)
170         {
171             std::string error = "GPU timer should be recording, but is "
172                                 + std::string((debugState_ == TimerState::Idle) ? "idle" : "stopped")
173                                 + ".";
174             GMX_ASSERT(debugState_ == TimerState::Recording, error.c_str());
175         }
176         return impl_.fetchNextEvent();
177     }
178 };
179
180 #endif