Support pinning in HostAllocator
[alexxy/gromacs.git] / src / gromacs / gpu_utils / hostallocator.h
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2017, 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 /*! \libinternal \file
36  * \brief Declares gmx::HostAllocationPolicy, gmx::HostAllocator, and
37  * gmx::HostVector, which are used to make/be standard library
38  * containers that can allocate memory suitable for transfers.
39  * Currently the only supported transfers using pinned memory are
40  * to CUDA GPUs, but other possibilities exist in future.
41  *
42  * \author Mark Abraham <mark.j.abraham@gmail.com>
43  * \inlibraryapi
44  */
45 #ifndef GMX_GPU_UTILS_HOSTALLOCATOR_H
46 #define GMX_GPU_UTILS_HOSTALLOCATOR_H
47
48 #include <cstddef>
49
50 #include <memory>
51 #include <vector>
52
53 #include "gromacs/utility/alignedallocator.h"
54 #include "gromacs/utility/exceptions.h"
55
56 namespace gmx
57 {
58
59 /*! \brief Helper enum for pinning policy of the allocation of
60  * HostAllocationPolicy.
61  *
62  * For an efficient non-blocking transfer (e.g. to a GPU), the memory
63  * pages for a buffer need to be pinned to a physical page. Aligning
64  * such buffers to a physical page should miminize the number of pages
65  * that need to be pinned. However, some buffers that may be used for
66  * such transfers may also be used in either GROMACS builds or run
67  * paths that cannot use such a device, so the policy can be
68  * configured so that the resource consumption is no higher than
69  * required for correct, efficient operation in all cases. */
70 enum class PinningPolicy : int
71 {
72     CannotBePinned,     // Memory is not known to be suitable for pinning.
73     CanBePinned,        // Memory is suitable for efficient pinning, e.g. because it is
74                         // allocated to be page aligned, and will be pinned when non-empty.
75 };
76
77 //! Forward declaration of host allocation policy class.
78 class HostAllocationPolicy;
79
80 /*! \brief Memory allocator that uses HostAllocationPolicy.
81  *
82  *  \tparam T          Type of objects to allocate
83  *
84  * This convenience partial specialization can be used for the
85  * optional allocator template parameter in standard library
86  * containers whose memory may be used for e.g. GPU transfers. The
87  * memory will always be allocated according to the behavior of
88  * HostAllocationPolicy.
89  */
90 template <class T>
91 using HostAllocator = Allocator<T, HostAllocationPolicy>;
92
93 //! Convenience alias for std::vector that uses HostAllocator.
94 template <class T>
95 using HostVector = std::vector<T, HostAllocator<T> >;
96
97 /*! \libinternal
98  * \brief Policy class for configuring gmx::Allocator, to manage
99  * allocations of memory that may be needed for e.g. GPU transfers.
100  *
101  * This allocator has state, so is most useful in cases where it is
102  * not known at compile time whether the allocated memory will be
103  * transferred to some device. It will increase the size of containers
104  * that use it. If the GROMACS build is configured with CUDA support,
105  * then memory will be allocated with PageAlignedAllocator, and that
106  * page pinned to physical memory if the pinning mode has been
107  * activated. If pinning mode is deactivated, or the GROMACS build
108  * does not support CUDA, then the memory will be allocated with
109  * AlignedAllocator. The pin() and unpin() methods work with the CUDA
110  * build, and silently do nothing otherwise. In future, we may modify
111  * or generalize this to work differently in other cases.
112  *
113  * The intended use is to configure gmx::Allocator with this class as
114  * its policy class, and then to use e.g.
115  * std::vector::get_allocator().getPolicy() to control whether the
116  * allocation policy should activate its pinning mode. The policy
117  * object can also be used to explicitly pin() and unpin() the buffer
118  * when it is using PinningPolicy::CanBePinned. The policy object is
119  * returned by value (as required by the C++ standard for
120  * get_allocator(), which copies a std::shared_ptr, so the policy
121  * object should be retrieved sparingly, e.g. only upon resize of the
122  * allocation. (Normal operation of the vector, e.g. during resize,
123  * incurs only the cost of the pointer indirection needed to consult
124  * the current state of the allocation policy.)
125  *
126  * \todo As a minor optimization, consider also having a stateless
127  * version of this policy, which might be slightly faster or more
128  * convenient to use in the cases where it is known at compile time
129  * that the allocation will be used to transfer to a GPU.
130  */
131 class HostAllocationPolicy
132 {
133     public:
134         //! Default constructor.
135         HostAllocationPolicy();
136         /*! \brief Return the alignment size currently used by the active pinning policy. */
137         std::size_t alignment();
138         /*! \brief Allocate and perhaps pin page-aligned memory suitable for
139          * e.g. GPU transfers.
140          *
141          * Before attempting to allocate, unpin() is called. After a
142          * successful allocation, pin() is called. (Whether these do
143          * things depends on the PinningPolicy that is in effect.)
144          *
145          *  \param bytes Amount of memory (bytes) to allocate. It is valid to ask for
146          *               0 bytes, which will return a non-null pointer that is properly
147          *               aligned and padded (but that you should not use).
148          *
149          *  \return Valid pointer if the allocation+optional pinning worked, otherwise nullptr.
150          *
151          *  \note Memory allocated with this routine must be released
152          *        with gmx::HostAllocationPolicy::free(), and
153          *        absolutely not the system free().
154          *
155          * Does not throw.
156          */
157         void *malloc(std::size_t bytes) const noexcept;
158         /*! \brief Free the memory, after unpinning (if appropriate).
159          *
160          *  \param buffer  Memory pointer previously returned from gmx::HostAllocationPolicy::malloc()
161          *
162          *  \note This routine should only be called with pointers
163          *        obtained from gmx:HostAllocationPolicy::malloc(),
164          *        and absolutely not any pointers obtained the system
165          *        malloc().
166          *
167          * Does not throw.
168          */
169         void free(void *buffer) const noexcept;
170         /*! \brief Pin the allocation to physical memory, if appropriate.
171          *
172          * If the allocation policy is not in pinning mode, or the
173          * allocation is empty, ot the allocation is already pinned,
174          * then do nothing.
175          *
176          * Does not throw.
177          */
178         void pin() const noexcept;
179         /*! \brief Unpin the allocation, if appropriate.
180          *
181          * Regardless of the allocation policy, unpin the memory if
182          * previously pinned, otherwise do nothing.
183          *
184          * Does not throw.
185          */
186         void unpin() const noexcept;
187         /*! \brief Return the current pinning policy (which is semi-independent
188          * of whether the buffer is actually pinned).
189          *
190          * Does not throw.
191          */
192         PinningPolicy pinningPolicy() const;
193         //! Specify an allocator trait so that the stateful allocator should propagate.
194         using propagate_on_container_copy_assignment = std::true_type;
195         //! Specify an allocator trait so that the stateful allocator should propagate.
196         using propagate_on_container_move_assignment = std::true_type;
197         //! Specify an allocator trait so that the stateful allocator should propagate.
198         using propagate_on_container_swap = std::true_type;
199     private:
200         /*! \brief Set the current pinning policy.
201          *
202          * Does not pin any current buffer. Use changePinningPolicy to
203          * orchestrate the necessary unpin, allocate, copy, pin for
204          * effectively changing the pinning policy of a HostVector.
205          *
206          * Does not throw.
207          */
208         // cppcheck-suppress unusedPrivateFunction
209         void setPinningPolicy(PinningPolicy pinningPolicy);
210         /*! \brief Declare as a friend function the only supported way
211          * to change the pinning policy.
212          *
213          * When the pinning policy changes, we want the state of the
214          * allocation to match the new policy. However, that requires
215          * a copy and swap of the buffers, which can only take place
216          * at the level of the container. So we wrap the required
217          * operations in a helper friend function.
218          *
219          * Of course, if there is no allocation because the vector is
220          * empty, then nothing will change. */
221         template <class T> friend
222         void changePinningPolicy(HostVector<T> *v, PinningPolicy pinningPolicy);
223         //! Private implementation class.
224         class Impl;
225         /*! \brief State of the allocator.
226          *
227          * This could change through move- or copy-assignment of one
228          * policy to another, so isn't const. */
229         std::shared_ptr<Impl> impl_;
230 };
231
232 /*! \brief Helper function for changing the pinning policy of a HostVector.
233  *
234  * If the vector has contents, then a full reallocation and buffer
235  * copy are needed if the policy change requires tighter restrictions,
236  * and desirable even if the policy change requires looser
237  * restrictions. That cost is OK, because GROMACS will do this
238  * operation very rarely (e.g. when auto-tuning and deciding to switch
239  * whether a task will run on a GPU, or not). */
240 template <class T>
241 void changePinningPolicy(HostVector<T> *v, PinningPolicy pinningPolicy)
242 {
243     // Do we have anything to do?
244     HostAllocationPolicy vAllocationPolicy = v->get_allocator().getPolicy();
245     if (pinningPolicy == vAllocationPolicy.pinningPolicy())
246     {
247         return;
248     }
249     // Make sure we never have two allocated buffers that are both pinned.
250     vAllocationPolicy.unpin();
251
252     // Construct a new vector that has the requested
253     // allocation+pinning policy, to swap into *v. If *v is empty,
254     // then no real work is done.
255     HostAllocator<T> newAllocator;
256     newAllocator.getPolicy().setPinningPolicy(pinningPolicy);
257     HostVector<T>    newV(v->begin(), v->end(), newAllocator);
258     // Replace the contents of *v, including the stateful allocator.
259     v->swap(newV);
260     // The destructor of newV cleans up the memory formerly managed by *v.
261 }
262
263 }      // namespace gmx
264
265 #endif