Introduce and use AlignedAllocationPolicy
authorMark Abraham <mark.j.abraham@gmail.com>
Mon, 3 Jul 2017 17:40:10 +0000 (19:40 +0200)
committerMark Abraham <mark.j.abraham@gmail.com>
Tue, 3 Oct 2017 02:15:02 +0000 (04:15 +0200)
This permits the allocators to be specialised with a policy class that
can vary (and report on) the alignment.

Change-Id: I875548ac325edcf07074ad35f9d90cdf561ea750

src/gromacs/ewald/pme-spline-work.cpp
src/gromacs/utility/alignedallocator.cpp
src/gromacs/utility/alignedallocator.h
src/gromacs/utility/allocator.h [new file with mode: 0644]
src/gromacs/utility/smalloc.cpp
src/gromacs/utility/tests/alignedallocator.cpp

index e5b8dc08443766037894a2fef3f00f6021180eff..5859a340455ffac1dfee4a854c89cf942181fe5b 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
  * Copyright (c) 2001-2004, The GROMACS development team.
- * Copyright (c) 2013,2014,2015, by the GROMACS development team, led by
+ * Copyright (c) 2013,2014,2015,2017, by the GROMACS development team, led by
  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
  * and including many others, as listed in the AUTHORS file in the
  * top-level source directory and at http://www.gromacs.org.
@@ -59,7 +59,7 @@ pme_spline_work *make_pme_spline_work(int gmx_unused order)
     Simd4Real        real_mask_S0, real_mask_S1;
     int              of, i;
 
-    work = new(internal::alignedMalloc(sizeof(pme_spline_work)))pme_spline_work;
+    work = new(gmx::AlignedAllocationPolicy::malloc(sizeof(pme_spline_work)))pme_spline_work;
 
     zero_S = setZero();
 
index 3c51372f8212bf9b29df71f7ba7ff9563782ade5..c32fa59868b7e418f1e4be6b53807b55313d4b8c 100644 (file)
@@ -37,6 +37,7 @@
  * Implements AlignedAllocator.
  *
  * \author Erik Lindahl <erik.lindahl@gmail.com>
+ * \author Mark Abraham <mark.j.abraham@gmail.com>
  * \ingroup module_utility
  */
 #include "gmxpre.h"
@@ -47,6 +48,8 @@
 
 #include <cstdlib>
 
+#include <memory>
+
 #if HAVE_MM_MALLOC_H
 #    include <mm_malloc.h>
 #elif HAVE_MALLOC_H
 #    include <xmmintrin.h>
 #endif
 
-#include "gromacs/utility/gmxassert.h"
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
 
+#include "gromacs/compat/make_unique.h"
+#include "gromacs/utility/gmxassert.h"
 
 namespace gmx
 {
 
-namespace internal
+namespace
 {
 
 /*! \brief Allocate aligned memory in a fully portable way
@@ -137,31 +144,11 @@ alignedFreeGeneric(void *p)
     }
 }
 
-
-
-void *
-alignedMalloc(std::size_t bytes)
+//! Implement malloc of \c bytes of memory, aligned to \c alignment.
+static void *mallocImpl(std::size_t bytes, std::size_t alignment)
 {
-    // For now we always use 128-byte alignment:
-    // 1) IBM Power already has cache lines of 128-bytes, and needs it.
-    // 2) x86 has 64 byte cache lines, but since a future AVX-1024 (rumored?)
-    //    will need 1024/8=128 byte SIMD alignment, it is safer to use that
-    //    already now.
-    // 3) The old Pentium4 used 256-byte cache prefetching (but 64-byte lines).
-    //    However, it's not worth worrying about performance for P4...
-    // 4) ARM & Sparc have 64 byte lines, but will be just fine with
-    //    128-byte alignment (nobody knows what the future brings)
-    //
-    // So, for now we're semi-lazy and just align to 128 bytes!
-    //
-    // TODO LINCS code is copying this assumption independently (for now)
-    std::size_t alignment = 128;
-
     void   *    p;
 
-    // Pad memory at the end with another alignment bytes to avoid false sharing
-    bytes += alignment;
-
 #if HAVE__MM_MALLOC
     p = _mm_malloc( bytes, alignment );
 #elif HAVE_POSIX_MEMALIGN
@@ -174,14 +161,14 @@ alignedMalloc(std::size_t bytes)
 #elif HAVE__ALIGNED_MALLOC
     p = _aligned_malloc(bytes, alignment);
 #else
-    p = alignedMallocGeneric(bytes, alignment);
+    p = internal::alignedMallocGeneric(bytes, alignment);
 #endif
 
     return p;
 }
 
-void
-alignedFree(void *p)
+//! Free aligned memory allocated with mallocImpl().
+static void freeImpl(void *p)
 {
     if (p)
     {
@@ -192,11 +179,95 @@ alignedFree(void *p)
 #elif HAVE__ALIGNED_MALLOC
         _aligned_free(p);
 #else
-        alignedFreeGeneric(p);
+        internal::alignedFreeGeneric(p);
 #endif
     }
 }
 
-} // namespace internal
+}   // namespace
+
+// === AlignedAllocationPolicy
+
+std::size_t AlignedAllocationPolicy::alignment()
+{
+    // For now we always use 128-byte alignment:
+    // 1) IBM Power already has cache lines of 128-bytes, and needs it.
+    // 2) x86 has 64 byte cache lines, but since a future AVX-1024 (rumored?)
+    //    will need 1024/8=128 byte SIMD alignment, it is safer to use that
+    //    already now.
+    // 3) The old Pentium4 used 256-byte cache prefetching (but 64-byte lines).
+    //    However, it's not worth worrying about performance for P4...
+    // 4) ARM & Sparc have 64 byte lines, but will be just fine with
+    //    128-byte alignment (nobody knows what the future brings)
+    //
+    // So, for now we're semi-lazy and just align to 128 bytes!
+    //
+    // TODO LINCS code is copying this assumption independently (for now)
+    return 128;
+}
+
+void *
+AlignedAllocationPolicy::malloc(std::size_t bytes)
+{
+    // Pad memory at the end with another alignment bytes to avoid false sharing
+    auto size = alignment();
+    bytes += size;
+
+    return mallocImpl(bytes, size);
+}
+
+void
+AlignedAllocationPolicy::free(void *p)
+{
+    freeImpl(p);
+}
+
+// === PageAlignedAllocationPolicy
+
+//! Return a page size, from a sysconf query if available, or a default guess (4096 bytes).
+static std::size_t getPageSize()
+{
+    long pageSize;
+    /* Note that sysconf returns -1 on its error conditions, which we
+       don't really need to check, nor can really handle at
+       initialization time. */
+#if defined(_SC_PAGESIZE)
+    pageSize = sysconf(_SC_PAGESIZE);
+#elif defined(_SC_PAGE_SIZE)
+    pageSize = sysconf(_SC_PAGE_SIZE);
+#else
+    pageSize = -1;
+#endif
+    return ((pageSize == -1) ? 4096 // A useful guess e.g. on Windows.
+            : static_cast<std::size_t>(pageSize));
+}
+
+/* Implements the "construct on first use" idiom to avoid the static
+ * initialization order fiasco where a possible static page-aligned
+ * container would be initialized before the alignment variable was.
+ *
+ * Note that thread-safety of the initialization is guaranteed by the
+ * C++11 language standard.
+ *
+ * The size_t has no destructor, so there is no deinitialization
+ * issue.  See https://isocpp.org/wiki/faq/ctors for discussion of
+ * alternatives and trade-offs. */
+std::size_t PageAlignedAllocationPolicy::alignment()
+{
+    static size_t thePageSize = getPageSize();
+    return thePageSize;
+}
+
+void *
+PageAlignedAllocationPolicy::malloc(std::size_t bytes)
+{
+    return mallocImpl(bytes, alignment());
+}
+
+void
+PageAlignedAllocationPolicy::free(void *p)
+{
+    freeImpl(p);
+}
 
 } // namespace gmx
index a86df70fb159ecc25f93b6242d90cb04853d4588..a9857849fbed759e308357f34491588bd3c28ca0 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2015,2016,2017, by the GROMACS development team, led by
+ * Copyright (c) 2017, by the GROMACS development team, led by
  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
  * and including many others, as listed in the AUTHORS file in the
  * top-level source directory and at http://www.gromacs.org.
  * the research papers on the package. Check out http://www.gromacs.org.
  */
 /*! \libinternal \file
- * \brief
- * Declares gmx::AlignedAllocator that is used to make standard library
- * containers compatible with SIMD contents that require aligned load/store.
+ * \brief Declares allocation policy classes and allocators that are
+ * used to make library containers compatible with alignment
+ * requirements of particular hardware, e.g. memory operations for
+ * SIMD or accelerators.
  *
  * \author Erik Lindahl <erik.lindahl@gmail.com>
+ * \author Mark Abraham <mark.j.abraham@gmail.com>
  * \inlibraryapi
  * \ingroup module_utility
  */
 
 #include <cstddef>
 
-#include <memory>
-#include <new>
-
-#include "gromacs/utility/basedefinitions.h"
+#include "gromacs/utility/allocator.h"
 
 namespace gmx
 {
 
-namespace internal
-{
-
-/*! \brief Allocate aligned memory
- *
- *  \param bytes Amount of memory (bytes) to allocate. It is valid to ask for
- *               0 bytes, which will return a non-null pointer that is properly
- *               aligned and padded (but that you should not use).
- *
- * \return Valid pointer if the allocation worked, otherwise nullptr.
- *
- * The memory will always be aligned to 128 bytes, which is our
- * estimate of the longest cache lines on architectures currently in use.
- * It will also be padded by the same amount at the end of the
- * area, to help avoid false cache sharing.
- *
- *  \note Memory allocated with this routine must be released with
- *        gmx::internal::alignedFree(), and absolutely not the system free().
- */
-void *
-alignedMalloc(std::size_t bytes);
-
-/*! \brief Free aligned memory
- *
- *  \param p  Memory pointer previously returned from gmx::internal::alignedMalloc()
- *
- *  \note This routine should only be called with pointers obtained from
- *        gmx::internal::alignedMalloc(), and absolutely not any pointers obtained
- *        the system malloc().
+/*! \libinternal \brief Policy class for configuring gmx::Allocator, to manage
+ * allocations of aligned memory for SIMD code.
  */
-void
-alignedFree(void *p);
-
-}
-
-/*! \libinternal \brief Aligned memory allocator.
- *
- *  \tparam T          Type of objects to allocate
- *
- * This class can be used for the optional allocator template parameter
- * in standard library containers, which is necessary e.g. to use SIMD
- * aligned load and store operations in those containers. The memory will always
- * be aligned to 128 bytes, which is our estimate of the longest cache lines on
- * architectures currently in use. It will also be padded by the same amount at
- * the end of the area, to help avoid false cache sharing.
- *
- * \throws std::bad_alloc Instead of a GROMACS exception object we throw the
- * standard one on allocation failures to make it as compatible as possible with
- * the errors expected by code using the standard library containers.
- *
- * \inlibraryapi
- * \ingroup module_utility
- */
-template <class T>
-class AlignedAllocator
+class AlignedAllocationPolicy
 {
     public:
-        // The standard library specification for a custom allocator
-        // requires these typedefs, with this capitalization/underscoring.
-        typedef T              value_type;      //!< Type of allocated elements
-        typedef T             &reference;       //!< Reference to allocated elements
-        typedef const T       &const_reference; //!< Constant reference to allocated elements
-        typedef T *            pointer;         //!< Pointer to allocated elements
-        typedef const T *      const_pointer;   //!< Constant pointer to allocated elements
-        typedef std::size_t    size_type;       //!< Integer type to use for size of objects
-        typedef std::ptrdiff_t difference_type; //!< Type to hold differences between pointers
-
-        /*! \libinternal \brief Standard-required typedef to use allocator with different class.
+        /*! \brief Return the alignment size. */
+        static std::size_t
+        alignment();
+        /*! \brief Allocate memory aligned to alignment() bytes.
          *
-         *  \tparam U new class
+         *  \param bytes Amount of memory (bytes) to allocate. It is valid to ask for
+         *               0 bytes, which will return a non-null pointer that is properly
+         *               aligned and padded (but that you should not use).
          *
-         *  This is used for things like std::list where the size of each link
-         *  is larger than the class stored in the link.
+         * \return Valid pointer if the allocation worked, otherwise nullptr.
          *
-         *  Required by the specification for an allocator.
-         */
-        template <class U>
-        struct rebind
-        {
-            typedef AlignedAllocator<U> other; //!< Align class U with our alignment
-        };
-
-        /*! \brief Templated copy constructor
+         * The memory will always be aligned to 128 bytes, which is our
+         * estimate of the longest cache lines on architectures currently in use.
+         * It will also be padded by the same amount at the end of the
+         * area, to help avoid false cache sharing.
          *
-         * This template constructor cannot be auto-generated, and is
-         * normally unused, except e.g. MSVC2015 standard library uses
-         * it in debug mode, presumably to implement some checks.
+         *  \note Memory allocated with this routine must be released with
+         *        gmx::AlignedAllocationPolicy::free(), and absolutely not the system free().
          */
-        template <class U>
-        explicit AlignedAllocator(const AlignedAllocator<U> &) {}
-
-        /*! \brief Constructor
+        static void *
+        malloc(std::size_t bytes);
+        /*! \brief Free aligned memory
          *
-         * No constructor can be auto-generated in the presence of any
-         * user-defined constructor, but we want the default constructor.
-         */
-        AlignedAllocator() {};
-
-        /*! \brief Return address of an object
-         *
-         *  \param r Reference to object of type T
-         *  \return Pointer to T memory
-         */
-        pointer
-        address(reference r) const { return &r; }
-
-        /*! \brief Return address of a const object
+         *  \param p  Memory pointer previously returned from malloc()
          *
-         *  \param r Const reference to object of type T
-         *  \return Pointer to T memory
+         *  \note This routine should only be called with pointers obtained from
+         *        gmx::AlignedAllocationPolicy::malloc(), and absolutely not any
+         *        pointers obtained the system malloc().
          */
-        const_pointer
-        address(const_reference r) const { return &r; }
-
-        /*! \brief Do the actual memory allocation
-         *
-         *  \param n    Number of elements of type T to allocate. n can be
-         *              0 bytes, which will return a non-null properly aligned
-         *              and padded pointer that should not be used.
-         *  \param hint Optional value returned from previous call to allocate.
-         *              For now this is not used.
-         *  \return Pointer to allocated memory
-         *
-         *  \throws std::bad_alloc if the allocation fails.
-         */
-        pointer
-        allocate(std::size_t n, typename std::allocator<void>::const_pointer gmx_unused hint = nullptr)
-        {
-            void *p = internal::alignedMalloc(n*sizeof(T));
+        static void
+        free(void *p);
+};
 
-            if (p == nullptr)
-            {
-                throw std::bad_alloc();
-            }
-            else
-            {
-                return static_cast<pointer>(p);
-            }
-        }
+/*! \brief Aligned memory allocator.
+ *
+ *  \tparam T          Type of objects to allocate
+ *
+ * This convenience partial specialization can be used for the
+ * optional allocator template parameter in standard library
+ * containers, which is necessary e.g. to use SIMD aligned load and
+ * store operations on data in those containers. The memory will
+ * always be aligned according to the behavior of
+ * AlignedAllocationPolicy.
+ */
+template <class T>
+using AlignedAllocator = Allocator<T, AlignedAllocationPolicy>;
 
-        /*! \brief Release memory
-         *
-         * \param p  Pointer to previously allocated memory returned from allocate()
-         * \param n  number of objects previously passed to allocate()
-         */
-        void
-        deallocate(pointer p, std::size_t gmx_unused n)
-        {
-            internal::alignedFree(p);
-        }
 
-        /*! \brief Construct an object without allocating memory
        *
-         * \tparam Args  Variable-length list of types for constructor args
-         * \param p      Adress of memory where to construct object
-         * \param args   Variable-length list of arguments to constructor
-         */
-        template<class ... Args>
-        void
-        construct(pointer p, Args && ... args) { ::new((void *)p)T(std::forward<Args>(args) ...); }
+/*! \brief Return the memory page size on this system
+ *
+ * Implements the "construct on first use" idiom to avoid the static
+ * initialization order fiasco where a possible static page-aligned
+ * container would be initialized before the alignment variable was.
+ *
+ * Note that thread-safety is guaranteed by the C++11 language
+ * standard. */
+std::size_t pageSize();
 
-        /*! \brief Call the destructor of object without releasing memory
+/*! \libinternal \brief Policy class for configuring gmx::Allocator,
+ * to manage allocations of page-aligned memory that can be locked for
+ * asynchronous transfer to GPU devices.
+ */
+class PageAlignedAllocationPolicy
+{
+    public:
+        /*! \brief Return the alignment size of memory pages on this system.
          *
-         * \param p  Address of memory where to destroy object
-         */
-        void
-        destroy(pointer p) { p->~value_type(); }
-
-        /*! \brief Return largest number of objects that can be allocated
+         * Guesses 4096 on Windows, otherwise queries sysconf. */
+        static std::size_t
+        alignment();
+        /*! \brief Allocate memory aligned to alignment() bytes.
          *
-         * This will be set such that the number of objects T multiplied by
-         * the size of each object is the largest value that can be represented
-         * by size_type.
-         */
-        std::size_t
-        max_size() const { return (static_cast<size_t>(0) - static_cast<size_t>(1)) / sizeof(T); }
-
-        /*! \brief Return true if two allocators are identical
+         *  \param bytes Amount of memory (bytes) to allocate. It is valid to ask for
+         *               0 bytes, which will return a non-null pointer that is properly
+         *               aligned and padded (but that you should not use).
          *
-         * \param rhs Other allocator
+         * \return Valid pointer if the allocation worked, otherwise nullptr.
          *
-         * This is a member function of the left-hand-side allocator.
+         *  \note Memory allocated with this routine must be released with
+         *        gmx::PageAlignedAllocationPolicy::free(), and absolutely not the system free().
          */
-        template<class T2>
-        bool
-        operator==(const AlignedAllocator<T2> &gmx_unused rhs) const { return std::is_same<T, T2>::value; GMX_UNUSED_VALUE(rhs); }
-
-        /*! \brief Return true if two allocators are different
+        static void *
+        malloc(std::size_t bytes);
+        /*! \brief Free aligned memory
          *
-         * \param rhs Other allocator.
+         *  \param p  Memory pointer previously returned from malloc()
          *
-         * This is a member function of the left-hand-side allocator.
+         *  \note This routine should only be called with pointers obtained from
+         *        gmx::PageAlignedAllocationPolicy::malloc(), and absolutely not any
+         *        pointers obtained the system malloc().
          */
-        bool
-        operator!=(const AlignedAllocator &rhs) const { return !operator==(rhs); }
+        static void
+        free(void *p);
 };
 
+/*! \brief PageAligned memory allocator.
+ *
+ *  \tparam T          Type of objects to allocate
+ *
+ * This convenience partial specialization can be used for the
+ * optional allocator template parameter in standard library
+ * containers, which is necessary for locking memory pages for
+ * asynchronous transfer between a GPU device and the host.  The
+ * memory will always be aligned according to the behavior of
+ * PageAlignedAllocationPolicy.
+ */
+template <class T>
+using PageAlignedAllocator = Allocator<T, PageAlignedAllocationPolicy>;
+
 }      // namespace gmx
 
 #endif // GMX_UTILITY_ALIGNEDALLOCATOR_H
diff --git a/src/gromacs/utility/allocator.h b/src/gromacs/utility/allocator.h
new file mode 100644 (file)
index 0000000..32b4b1a
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2017, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \libinternal \file
+ * \brief
+ * Declares gmx::Allocator template whose allocation functionality is
+ * configured both by type of object allocated and a policy class that
+ * configures the necessary matching malloc and free operation.
+ *
+ * \author Erik Lindahl <erik.lindahl@gmail.com>
+ * \author Mark Abraham <mark.j.abraham@gmail.com>
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
+#ifndef GMX_UTILITY_ALLOCATOR_H
+#define GMX_UTILITY_ALLOCATOR_H
+
+#include <cstddef>
+
+#include <memory>
+#include <new>
+
+#include "gromacs/utility/basedefinitions.h"
+
+namespace gmx
+{
+
+/*! \libinternal \brief Policy-based memory allocator.
+ *
+ *  \tparam T                 Type of objects to allocate
+ *  \tparam AllocationPolicy  Policy of (matching) allocation and deallocation functions.
+ *
+ * This class can be used for the optional allocator template
+ * parameter in standard library containers. It must be configured
+ * with both the type of object to allocate, and an AllocationPolicy
+ * which effectively wraps a matching pair of malloc and free
+ * functions. This permits implementing a family of related allocators
+ * e.g. with SIMD alignment, GPU host-side page locking, or perhaps
+ * both, in a way that preserves a common programming interface and
+ * duplicates minimal code.
+
+ * \throws std::bad_alloc Instead of a GROMACS exception object, we
+ * throw the standard one on allocation failures to make it as
+ * compatible as possible with the errors expected by code using the
+ * standard library containers.
+ *
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
+template <class T, typename AllocationPolicy>
+class Allocator
+{
+    public:
+        // The standard library specification for a custom allocator
+        // requires these typedefs, with this capitalization/underscoring.
+        typedef T              value_type;      //!< Type of allocated elements
+        typedef T             &reference;       //!< Reference to allocated elements
+        typedef const T       &const_reference; //!< Constant reference to allocated elements
+        typedef T *            pointer;         //!< Pointer to allocated elements
+        typedef const T *      const_pointer;   //!< Constant pointer to allocated elements
+        typedef std::size_t    size_type;       //!< Integer type to use for size of objects
+        typedef std::ptrdiff_t difference_type; //!< Type to hold differences between pointers
+
+        // This typedef is required by GROMACS for testing and assertions
+        typedef AllocationPolicy allocation_policy; //!< Type of the AllocationPolicy
+
+        /*! \libinternal \brief Standard-required typedef to use allocator with different class.
+         *
+         *  \tparam U new class
+         *
+         *  This is used for things like std::list where the size of each link
+         *  is larger than the class stored in the link.
+         *
+         *  Required by the specification for an allocator.
+         */
+        template <class U>
+        struct rebind
+        {
+            typedef Allocator<U, AllocationPolicy> other; //!< Align class U with our alignment
+        };
+
+        /*! \brief Templated copy constructor
+         *
+         * This template constructor cannot be auto-generated, and is
+         * normally unused, except e.g. MSVC2015 standard library uses
+         * it in debug mode, presumably to implement some checks.
+         */
+        template <class U>
+        explicit Allocator(const Allocator<U, AllocationPolicy> &) {}
+
+        /*! \brief Constructor
+         *
+         * No constructor can be auto-generated in the presence of any
+         * user-defined constructor, but we want the default constructor.
+         */
+        Allocator() {};
+
+        /*! \brief Return address of an object
+         *
+         *  \param r Reference to object of type T
+         *  \return Pointer to T memory
+         */
+        pointer
+        address(reference r) const { return &r; }
+
+        /*! \brief Return address of a const object
+         *
+         *  \param r Const reference to object of type T
+         *  \return Pointer to T memory
+         */
+        const_pointer
+        address(const_reference r) const { return &r; }
+
+        /*! \brief Do the actual memory allocation
+         *
+         *  \param n    Number of elements of type T to allocate. n can be
+         *              0 bytes, which will return a non-null properly aligned
+         *              and padded pointer that should not be used.
+         *  \param hint Optional value returned from previous call to allocate.
+         *              For now this is not used.
+         *  \return Pointer to allocated memory
+         *
+         *  \throws std::bad_alloc if the allocation fails.
+         */
+        pointer
+        allocate(std::size_t n, typename std::allocator<void>::const_pointer gmx_unused hint = nullptr)
+        {
+            void *p = AllocationPolicy::malloc(n*sizeof(T));
+
+            if (p == nullptr)
+            {
+                throw std::bad_alloc();
+            }
+            else
+            {
+                return static_cast<pointer>(p);
+            }
+        }
+
+        /*! \brief Release memory
+         *
+         * \param p  Pointer to previously allocated memory returned from allocate()
+         * \param n  number of objects previously passed to allocate()
+         */
+        void
+        deallocate(pointer p, std::size_t gmx_unused n)
+        {
+            AllocationPolicy::free(p);
+        }
+
+        /*! \brief Construct an object without allocating memory
+         *
+         * \tparam Args  Variable-length list of types for constructor args
+         * \param p      Adress of memory where to construct object
+         * \param args   Variable-length list of arguments to constructor
+         */
+        template<class ... Args>
+        void
+        construct(pointer p, Args && ... args) { ::new((void *)p)T(std::forward<Args>(args) ...); }
+
+        /*! \brief Call the destructor of object without releasing memory
+         *
+         * \param p  Address of memory where to destroy object
+         */
+        void
+        destroy(pointer p) { p->~value_type(); }
+
+        /*! \brief Return largest number of objects that can be allocated
+         *
+         * This will be set such that the number of objects T multiplied by
+         * the size of each object is the largest value that can be represented
+         * by size_type.
+         */
+        std::size_t
+        max_size() const { return (static_cast<size_t>(0) - static_cast<size_t>(1)) / sizeof(T); }
+
+        /*! \brief Return true if two allocators are identical
+         *
+         * This is a member function of the left-hand-side allocator.
+         */
+        template<class T2>
+        bool
+        operator==(const Allocator<T2, AllocationPolicy> &) const { return std::is_same<T, T2>::value; }
+
+        /*! \brief Return true if two allocators are different
+         *
+         * \param rhs Other allocator.
+         *
+         * This is a member function of the left-hand-side allocator.
+         */
+        bool
+        operator!=(const Allocator &rhs) const { return !operator==(rhs); }
+};
+
+}      // namespace gmx
+
+#endif
index 699681faa63f16586b46e7ed31da4554f043cbba..8087d389029fa85bdf4e069a3bb6aeae16d7b4aa 100644 (file)
@@ -262,11 +262,11 @@ void *save_malloc_aligned(const char *name, const char *file, int line,
                   "Cannot allocate aligned memory with alignment of zero!\n(called from file %s, line %d)", file, line);
     }
 
-    // Our new alignedMalloc always returns 128-byte aligned memory.
-    if (alignment > 128)
+    size_t alignmentSize = gmx::AlignedAllocationPolicy::alignment();
+    if (alignment > alignmentSize)
     {
         gmx_fatal(errno, __FILE__, __LINE__,
-                  "Cannot allocate aligned memory with alignment > 128 bytes\n(called from file %s, line %d)", file, line);
+                  "Cannot allocate aligned memory with alignment > %u bytes\n(called from file %s, line %d)", alignmentSize, file, line);
     }
 
 
@@ -285,7 +285,7 @@ void *save_malloc_aligned(const char *name, const char *file, int line,
         }
 #endif
 
-        p = gmx::internal::alignedMalloc(nelem*elsize);
+        p = gmx::AlignedAllocationPolicy::malloc(nelem*elsize);
 
         if (p == nullptr)
         {
@@ -310,7 +310,7 @@ void *save_calloc_aligned(const char *name, const char *file, int line,
 /* This routine can NOT be called with any pointer */
 void save_free_aligned(const char gmx_unused *name, const char gmx_unused *file, int gmx_unused line, void *ptr)
 {
-    gmx::internal::alignedFree(ptr);
+    gmx::AlignedAllocationPolicy::free(ptr);
 }
 
 void set_over_alloc_dd(gmx_bool set)
index 01a23ed7ade0b5bc2c6306f10819b6f608ab0f06..d07585d28c291697879980da900a941d24008559 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2015, by the GROMACS development team, led by
+ * Copyright (c) 2015,2017, by the GROMACS development team, led by
  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
  * and including many others, as listed in the AUTHORS file in the
  * top-level source directory and at http://www.gromacs.org.
@@ -36,6 +36,7 @@
  * \brief Tests for gmx::AlignedAllocator
  *
  * \author Erik Lindahl <erik.lindahl@gmail.com>
+ * \author Mark Abraham <mark.j.abraham@gmail.com>
  * \ingroup module_utility
  */
 
 
 #include "gromacs/utility/alignedallocator.h"
 
-#include <list>
 #include <vector>
 
 #include <gtest/gtest.h>
 
+#include "gromacs/math/vectypes.h"
 #include "gromacs/utility/real.h"
 
 namespace gmx
 {
 
-TEST(AlignedAllocatorTest, AllocatorAlign)
+//! Templated test fixture.
+template <typename T>
+class AllocatorTest : public ::testing::Test
 {
-    AlignedAllocator<real>   a;
-    real *                   p    = a.allocate(1000);
+    public:
+        /*! \brief Bitmask for testing the alignment.
+         *
+         * e.g. for 128-byte alignment the mask is 128-1 - all of
+         * these bits should be zero in pointers that have the
+         * intended alignment. */
+        std::size_t mask_ = T::allocation_policy::alignment()-1;
+};
 
-    // Mask for 128-byte alignment is 128-1 - these bits should be zero in p
-    std::size_t              mask = static_cast<std::size_t>(128-1);
+//! Declare allocator types to test.
+using AllocatorTypesToTest = ::testing::Types<AlignedAllocator<real>,
+                                              PageAlignedAllocator<real>,
+                                              AlignedAllocator<int>,
+                                              PageAlignedAllocator<int>,
+                                              AlignedAllocator<RVec>,
+                                              PageAlignedAllocator<RVec>
+                                              >;
+TYPED_TEST_CASE(AllocatorTest, AllocatorTypesToTest);
 
-    EXPECT_EQ(0, reinterpret_cast<std::size_t>(p) & mask);
+// NB need to use this->mask_ because of GoogleTest quirks
+
+TYPED_TEST(AllocatorTest, AllocatorAlignAllocatesWithAlignment)
+{
+    using pointer = typename TypeParam::pointer;
+    TypeParam a;
+    pointer   p = a.allocate(1000);
+
+    EXPECT_EQ(0, reinterpret_cast<std::size_t>(p) & this->mask_);
     a.deallocate(p, 1000);
 }
 
 
-TEST(AlignedAllocator, Vector)
+TYPED_TEST(AllocatorTest, VectorAllocatesAndResizesWithAlignment)
 {
-    // Mask for 128-byte alignment is 128-1 - these bits should be zero in pointers
-    std::size_t mask = static_cast<std::size_t>(128-1);
+    using value_type = typename TypeParam::value_type;
+    std::vector<value_type, TypeParam> v(10);
+    EXPECT_EQ(0, reinterpret_cast<std::size_t>(v.data()) & this->mask_);
 
-    std::vector<real, AlignedAllocator<real> > v(10);
-    EXPECT_EQ(0, reinterpret_cast<std::size_t>(v.data()) & mask);
-
-    for (std::size_t i = 10000; i <= 100000; i += 10000)
+    // Reserve a few times to check things work ok, making sure we
+    // will trigger several reallocations on common vector
+    // implementations.
+    for (std::size_t i = 1000; i <= 10000; i += 1000)
     {
         v.resize(i);
-        EXPECT_EQ(0, reinterpret_cast<std::size_t>(v.data()) & mask);
+        EXPECT_EQ(0, reinterpret_cast<std::size_t>(v.data()) & this->mask_);
     }
 }
 
+TYPED_TEST(AllocatorTest, VectorAllocatesAndReservesWithAlignment)
+{
+    using value_type = typename TypeParam::value_type;
+    std::vector<value_type, TypeParam> v(10);
+    EXPECT_EQ(0, reinterpret_cast<std::size_t>(v.data()) & this->mask_);
+
+    // Reserve a few times to check things work ok, making sure we
+    // will trigger several reallocations on common vector
+    // implementations.
+    for (std::size_t i = 1000; i <= 10000; i += 1000)
+    {
+        v.reserve(i);
+        EXPECT_EQ(0, reinterpret_cast<std::size_t>(v.data()) & this->mask_);
+    }
+}
 
 }