2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2016,2017,2018,2019,2020,2021, 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.
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.
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.
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.
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.
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.
37 * Declares gmx::PaddedRVecVector
39 * \author Mark Abraham <mark.j.abraham@gmail.com>
41 * \ingroup module_math
43 #ifndef GMX_MATH_PADDEDVECTOR_H
44 #define GMX_MATH_PADDEDVECTOR_H
50 #include "gromacs/math/arrayrefwithpadding.h"
51 #include "gromacs/math/vectypes.h"
52 #include "gromacs/utility/alignedallocator.h"
53 #include "gromacs/utility/allocator.h"
54 #include "gromacs/utility/arrayref.h"
62 /*! \brief Traits classes for handling padding for types used with PaddedVector
64 * Only the base types of the SIMD module are supported for
65 * PaddedVector, because the purpose of the padding is to permit
66 * SIMD-width operations from the SIMD module.
68 * \todo Consider explicitly tying these types to the SimdTrait
69 * types. That would require depending on the SIMD module, or
70 * extracting the traits from it. This would also permit
71 * maxSimdWidthOfBaseType to be set more efficiently, e.g. as a
72 * metaprogramming max over the maximum width from different
81 struct PaddingTraits<int32_t>
83 using SimdBaseType = int32_t;
84 static constexpr int maxSimdWidthOfBaseType = 16;
88 struct PaddingTraits<float>
90 using SimdBaseType = float;
91 static constexpr int maxSimdWidthOfBaseType = GMX_FLOAT_MAX_SIMD_WIDTH;
95 struct PaddingTraits<double>
97 using SimdBaseType = double;
98 static constexpr int maxSimdWidthOfBaseType = GMX_DOUBLE_MAX_SIMD_WIDTH;
102 struct PaddingTraits<BasicVector<float>>
104 using SimdBaseType = float;
105 static constexpr int maxSimdWidthOfBaseType = GMX_FLOAT_MAX_SIMD_WIDTH;
109 struct PaddingTraits<BasicVector<double>>
111 using SimdBaseType = double;
112 static constexpr int maxSimdWidthOfBaseType = GMX_DOUBLE_MAX_SIMD_WIDTH;
115 /*! \brief Returns the allocation size for PaddedVector that contains
116 * \c numElements elements plus padding for SIMD operations.
118 * \param[in] numElements The number of T elements for which data will be stored.
119 * \returns The number of T elements that must be allocated
120 * (ie >= numElements).
123 index computePaddedSize(index numElements)
125 // We don't need padding if there is no access.
126 if (numElements == 0)
131 // We sometimes load a whole extra element when doing 4-wide SIMD
132 // operations (which might e.g. be an RVec) so we need to pad for
134 index simdScatterAccessSize = numElements + 1;
136 // For SIMD updates based on RVec, we might load starting from
137 // the last RVec element, so that sets the minimum extent of the
138 // padding. That extent must take the initialized allocation up to
139 // the SIMD width of the base type multiplied by the width of T in
140 // that base type. But since storage_ contains RVec, we only have
141 // to tell it the number of elements, which means to round up to
142 // the next SIMD width.
144 // We don't want a dependence on the SIMD module for the actual
145 // SIMD width of the base type, so we use maximum for the base
146 // type via the traits. A little extra padding won't really hurt.
147 constexpr int maxSimdWidth = PaddingTraits<T>::maxSimdWidthOfBaseType;
148 index simdFlatAccessSize = (numElements + (maxSimdWidth - 1)) / maxSimdWidth * maxSimdWidth;
150 return std::max(simdScatterAccessSize, simdFlatAccessSize);
153 //! Helper function to insert padding elements for most T.
154 template<typename T, typename AllocatorType>
155 inline void insertPaddingElements(std::vector<T, AllocatorType>* v, index newPaddedSize)
157 // Ensure the padding region is initialized to zero. There is no
158 // way to insert a number of default-initialized elements. So we
159 // have to provide a value for those elements, which anyway suits
161 v->insert(v->end(), newPaddedSize - v->size(), 0);
164 //! Specialization of helper function to insert padding elements, used for BasicVector<T>.
165 template<typename T, typename AllocatorType>
166 inline void insertPaddingElements(std::vector<BasicVector<T>, AllocatorType>* v, index newPaddedSize)
168 // Ensure the padding region is initialized to zero.
169 v->insert(v->end(), newPaddedSize - v->size(), BasicVector<T>(0, 0, 0));
172 } // namespace detail
174 /*! \brief PaddedVector is a container of elements in contiguous
175 * storage that allocates extra memory for safe SIMD-style loads for
176 * operations used in GROMACS.
178 * \tparam T the type of objects within the container
179 * \tparam Allocator the allocator used. Can be any standard-compliant
180 * allocator, such gmx::Allocator used for alignment and/or pinning.
182 * The interface resembles std::vector. However, access
183 * intended to include padded elements must be via ArrayRef objects
184 * explicitly created to view those elements. Most other aspects of
185 * this vector refer to the unpadded view, e.g. iterators, data(),
188 * The underlying storage is allocated with extra elements, properly
189 * initialized, that ensure that any operations accessing the any
190 * non-additional element that operate on memory equivalent to a full
191 * SIMD lane do so on allocated memory that has been initialized, so
192 * that memory traps will not occur, and arithmetic operations will
193 * not cause e.g. floating-point exceptions so long as the values in
194 * the padded elements are properly managed.
196 * Proper initialization is tricker than it would first appear, since
197 * we intend this container to be used with scalar and class types
198 * (e.g. RVec). Resize and construction operations use "default
199 * insertion" which leads to zero initialization for the former, and
200 * calling the default constructor for the latter. BasicVector has a
201 * default constructor that leaves the elements uninitialized, which
202 * is particularly risky for elements only present as padding. Thus
203 * the implementation specifically initializes the padded elements to
204 * zero, which makes no difference to the scalar template
205 * instantiations, and makes the BasicVector ones safer to use.
207 * Because the allocator can be configured, the memory allocation can
208 * have other attributes such as SIMD alignment or being pinned to
209 * physical memory for efficient transfers. The default allocator
210 * ensures alignment, but std::allocator also works.
212 template<typename T, typename Allocator = Allocator<T, AlignedAllocationPolicy>>
216 //! Standard helper types
218 using value_type = T;
219 using allocator_type = Allocator;
220 using size_type = index;
221 using reference = value_type&;
222 using const_reference = const value_type&;
223 using storage_type = std::vector<T, allocator_type>;
224 using pointer = typename storage_type::pointer;
225 using const_pointer = typename storage_type::const_pointer;
226 using iterator = typename storage_type::iterator;
227 using const_iterator = typename storage_type::const_iterator;
228 using difference_type = typename storage_type::iterator::difference_type;
231 PaddedVector() : storage_(), unpaddedEnd_(begin()) {}
232 /*! \brief Constructor that specifies the initial size. */
233 explicit PaddedVector(size_type count, const allocator_type& allocator = Allocator()) :
234 storage_(count, allocator), unpaddedEnd_(begin() + count)
236 // The count elements have been default inserted, and now
237 // the padding elements are added
238 resizeWithPadding(count);
240 /*! \brief Constructor that specifies the initial size and an element to copy. */
241 explicit PaddedVector(size_type count, value_type const& v, const allocator_type& allocator = Allocator()) :
242 storage_(count, v, allocator), unpaddedEnd_(begin() + count)
244 // The count elements have been default inserted, and now
245 // the padding elements are added
246 resizeWithPadding(count);
248 //! Default constructor with allocator
249 explicit PaddedVector(allocator_type const& allocator) :
250 storage_(allocator), unpaddedEnd_(begin())
254 PaddedVector(PaddedVector const& o) : storage_(o.storage_), unpaddedEnd_(begin() + o.size()) {}
255 /*! \brief Move constructor
257 * Leaves \c o in a valid state (ie the destructor can be
259 PaddedVector(PaddedVector&& o) noexcept :
260 storage_(std::exchange(o.storage_, {})), unpaddedEnd_(o.unpaddedEnd_)
263 /*! \brief Move constructor using \c alloc for the new vector.
265 * Note that \c alloc is another instance of the same allocator
266 * type as used for \c PaddedVector. This makes sense e.g. for
267 * stateful allocators such as HostAllocator used in
270 * Leaves \c o in a valid state (ie. the destructor can be
272 PaddedVector(PaddedVector&& o, const Allocator& alloc) noexcept :
273 storage_(alloc), unpaddedEnd_(begin())
275 if (alloc == o.storage_.get_allocator())
277 std::swap(storage_, o.storage_);
278 unpaddedEnd_ = o.unpaddedEnd_;
282 // If the allocator compares differently, we must
283 // reallocate and copy.
284 auto unpaddedSize = o.size();
285 resizeWithPadding(unpaddedSize);
286 std::copy(o.begin(), o.end(), storage_.begin());
287 unpaddedEnd_ = begin() + unpaddedSize;
290 //! Construct from an initializer list
291 PaddedVector(std::initializer_list<value_type> const& il) :
292 storage_(il), unpaddedEnd_(storage_.end())
294 // We can't choose the padding until we know the size of
295 // the normal vector, so we have to make the storage_ and
297 resizeWithPadding(storage_.size());
299 //! Reserve storage for the container to contain newExtent elements, plus the required padding.
300 void reserveWithPadding(const size_type newExtent)
302 auto unpaddedSize = end() - begin();
303 /* v.reserve(13) should allocate enough memory so that
304 v.resize(13) does not reallocate. This means that the
305 new extent should be large enough for the padded
306 storage for a vector whose size is newExtent. */
307 auto newPaddedExtent = detail::computePaddedSize<T>(newExtent);
308 storage_.reserve(newPaddedExtent);
309 unpaddedEnd_ = begin() + unpaddedSize;
311 //! Resize the container to contain newSize elements, plus the required padding.
312 void resizeWithPadding(const size_type newSize)
314 // When the contained type is e.g. a scalar, then the
315 // default initialization behaviour is to zero all
316 // elements, which is OK, but we have to make sure that it
317 // happens for the elements in the padded region when the
318 // vector is shrinking.
319 auto newPaddedSize = detail::computePaddedSize<T>(newSize);
320 // Make sure there is room for padding if we need to grow.
321 storage_.reserve(newPaddedSize);
322 // Make the unpadded size correct, with any additional
323 // elements initialized by the default constructor. It is
324 // particularly important to destruct former elements when
325 // newSize is smaller than the old size.
326 storage_.resize(newSize);
327 // Ensure the padding region is zeroed if required.
328 detail::insertPaddingElements(&storage_, newPaddedSize);
329 unpaddedEnd_ = begin() + newSize;
331 //! Return the size of the view without the padding.
332 size_type size() const { return end() - begin(); }
333 //! Return the container size including the padding.
334 size_type paddedSize() const { return storage_.size(); }
335 //! Return whether the storage is empty.
336 bool empty() const { return storage_.empty(); }
337 //! Swap two PaddedVectors
338 void swap(PaddedVector& x)
340 std::swap(storage_, x.storage_);
341 std::swap(unpaddedEnd_, x.unpaddedEnd_);
343 //! Clear the vector, ie. set size to zero and remove padding.
347 unpaddedEnd_ = begin();
349 //! Iterator getters refer to a view without padding.
351 pointer data() noexcept { return storage_.data(); }
352 const_pointer data() const noexcept { return storage_.data(); }
354 iterator begin() { return storage_.begin(); }
355 iterator end() { return iterator(unpaddedEnd_); }
357 const_iterator cbegin() { return const_iterator(begin()); }
358 const_iterator cend() { return const_iterator(unpaddedEnd_); }
360 const_iterator begin() const { return storage_.begin(); }
361 const_iterator end() const { return const_iterator(unpaddedEnd_); }
363 const_iterator cbegin() const { return const_iterator(begin()); }
364 const_iterator cend() const { return const_iterator(unpaddedEnd_); }
366 // TODO should these do bounds checking for the unpadded range? In debug mode?
367 //! Indexing operator.
368 reference operator[](int i) { return storage_[i]; }
369 //! Indexing operator as const.
370 const_reference operator[](int i) const { return storage_[i]; }
371 //! Returns an ArrayRef of elements that includes the padding region, e.g. for use in SIMD code.
372 ArrayRefWithPadding<T> arrayRefWithPadding()
374 return ArrayRefWithPadding<T>(data(), data() + size(), data() + paddedSize());
376 //! Returns an ArrayRef of const elements that includes the padding region, e.g. for use in SIMD code.
377 ArrayRefWithPadding<const T> constArrayRefWithPadding() const
379 return ArrayRefWithPadding<const T>(data(), data() + size(), data() + paddedSize());
381 /*! \brief Returns an rvec * pointer for containers of RVec, for use with legacy code.
383 * \todo Use std::is_same_v when CUDA 11 is a requirement.
385 template<typename AlsoT = T, typename = typename std::enable_if<std::is_same<AlsoT, RVec>::value>>
388 return as_rvec_array(data());
390 /*! \brief Returns a const rvec * pointer for containers of RVec, for use with legacy code.
392 * \todo Use std::is_same_v when CUDA 11 is a requirement.
394 template<typename AlsoT = T, typename = typename std::enable_if<std::is_same<AlsoT, RVec>::value>>
395 const rvec* rvec_array() const
397 return as_rvec_array(data());
399 //! Copy assignment operator
400 PaddedVector& operator=(PaddedVector const& o)
404 storage_ = o.storage_;
405 unpaddedEnd_ = begin() + o.size();
409 //! Move assignment operator
410 PaddedVector& operator=(PaddedVector&& o) noexcept
414 auto oSize = o.size();
415 storage_ = std::move(o.storage_);
416 unpaddedEnd_ = begin() + oSize;
417 o.unpaddedEnd_ = o.begin();
421 //! Getter for the allocator
422 allocator_type get_allocator() const { return storage_.get_allocator(); }
425 storage_type storage_;
426 iterator unpaddedEnd_;
431 // TODO These are hacks to avoid littering gmx:: all over code that is
432 // almost all destined to move into the gmx namespace at some point.
433 // An alternative would be about 20 files with using statements.
434 using gmx::PaddedVector; //NOLINT(google-global-names-in-headers)