989 lines
36 KiB
C++
989 lines
36 KiB
C++
// Copyright (c) 2010-2020, Lawrence Livermore National Security, LLC. Produced
|
|
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
|
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
|
//
|
|
// This file is part of the MFEM library. For more information and source code
|
|
// availability visit https://mfem.org.
|
|
//
|
|
// MFEM is free software; you can redistribute it and/or modify it under the
|
|
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
|
// CONTRIBUTING.md for details.
|
|
|
|
#ifndef MFEM_MEM_MANAGER_HPP
|
|
#define MFEM_MEM_MANAGER_HPP
|
|
|
|
#include "globals.hpp"
|
|
#include "error.hpp"
|
|
#include <cstring> // std::memcpy
|
|
#include <type_traits> // std::is_const
|
|
#include <cstddef> // std::max_align_t
|
|
|
|
namespace mfem
|
|
{
|
|
|
|
// Implementation of MFEM's lightweight device/host memory manager designed to
|
|
// work seamlessly with the OCCA, RAJA, and other kernels supported by MFEM.
|
|
|
|
/// Memory types supported by MFEM.
|
|
enum class MemoryType
|
|
{
|
|
HOST, ///< Host memory; using new[] and delete[]
|
|
HOST_32, ///< Host memory; aligned at 32 bytes
|
|
HOST_64, ///< Host memory; aligned at 64 bytes
|
|
HOST_DEBUG, ///< Host memory; allocated from a "host-debug" pool
|
|
HOST_UMPIRE, ///< Host memory; using Umpire
|
|
MANAGED, /**< Managed memory; using CUDA or HIP *MallocManaged
|
|
and *Free */
|
|
DEVICE, ///< Device memory; using CUDA or HIP *Malloc and *Free
|
|
DEVICE_DEBUG, /**< Pseudo-device memory; allocated on host from a
|
|
"device-debug" pool */
|
|
DEVICE_UMPIRE, ///< Device memory; using Umpire
|
|
SIZE ///< Number of host and device memory types
|
|
};
|
|
|
|
/// Static casts to 'int' and sizes of some useful memory types.
|
|
constexpr int MemoryTypeSize = static_cast<int>(MemoryType::SIZE);
|
|
constexpr int HostMemoryType = static_cast<int>(MemoryType::HOST);
|
|
constexpr int HostMemoryTypeSize = static_cast<int>(MemoryType::DEVICE);
|
|
constexpr int DeviceMemoryType = static_cast<int>(MemoryType::MANAGED);
|
|
constexpr int DeviceMemoryTypeSize = MemoryTypeSize - DeviceMemoryType;
|
|
|
|
/// Memory type names, used during Device:: configuration.
|
|
extern const char *MemoryTypeName[MemoryTypeSize];
|
|
|
|
/// Memory classes identify sets of memory types.
|
|
/** This type is used by kernels that can work with multiple MemoryType%s.
|
|
* For example, kernels that can use DEVICE or MANAGED memory types should
|
|
* use MemoryClass::DEVICE for their inputs. */
|
|
enum class MemoryClass
|
|
{
|
|
HOST, /**< Memory types: { HOST, HOST_32, HOST_64, HOST_DEBUG,
|
|
HOST_UMPIRE, MANAGED } */
|
|
HOST_32, ///< Memory types: { HOST_32, HOST_64, HOST_DEBUG }
|
|
HOST_64, ///< Memory types: { HOST_64, HOST_DEBUG }
|
|
DEVICE, ///< Memory types: { DEVICE, DEVICE_DEBUG, DEVICE_UMPIRE, MANAGED }
|
|
MANAGED ///< Memory types: { MANAGED }
|
|
};
|
|
|
|
/// Return true if the given memory type is in MemoryClass::HOST.
|
|
inline bool IsHostMemory(MemoryType mt) { return mt <= MemoryType::MANAGED; }
|
|
inline bool IsDeviceMemory(MemoryType mt) { return mt >= MemoryType::MANAGED; }
|
|
|
|
/// Return a suitable MemoryType for a given MemoryClass.
|
|
MemoryType GetMemoryType(MemoryClass mc);
|
|
|
|
/// Return a suitable MemoryClass from a pair of MemoryClass%es.
|
|
/** Note: this operation is commutative, i.e. a*b = b*a, associative, i.e.
|
|
(a*b)*c = a*(b*c), and has an identity element: MemoryClass::HOST.
|
|
|
|
Currently, the operation is defined as a*b := max(a,b) where the max
|
|
operation is based on the enumeration ordering:
|
|
|
|
HOST < HOST_32 < HOST_64 < DEVICE < MANAGED. */
|
|
MemoryClass operator*(MemoryClass mc1, MemoryClass mc2);
|
|
|
|
/// Class used by MFEM to store pointers to host and/or device memory.
|
|
/** The template class parameter, T, must be a plain-old-data (POD) type.
|
|
|
|
In many respects this class behaves like a pointer:
|
|
* When destroyed, a Memory object does NOT automatically delete any
|
|
allocated memory.
|
|
* Only the method Delete() will deallocate a Memory object.
|
|
* Other methods that modify the object (e.g. New(), Wrap(), etc) will simply
|
|
overwrite the old contents.
|
|
* One difference with a pointer is that a const Memory object does not allow
|
|
modification of the content (unlike e.g. a const pointer).
|
|
|
|
A Memory object stores up to two different pointers: one host pointer (with
|
|
MemoryType from MemoryClass::HOST) and one device pointer (currently one of
|
|
MemoryType: DEVICE, DEVICE_DEBUG, DEVICE_UMPIRE or MANAGED).
|
|
|
|
A Memory object can hold (wrap) an externally allocated pointer with any
|
|
given MemoryType.
|
|
|
|
Access to the content of the Memory object can be requested with any given
|
|
MemoryClass through the methods ReadWrite(), Read(), and Write().
|
|
Requesting such access may result in additional (internally handled)
|
|
memory allocation and/or memory copy.
|
|
* When ReadWrite() is called, the returned pointer becomes the only
|
|
valid pointer.
|
|
* When Read() is called, the returned pointer becomes valid, however
|
|
the other pointer (host or device) may remain valid as well.
|
|
* When Write() is called, the returned pointer becomes the only valid
|
|
pointer, however, unlike ReadWrite(), no memory copy will be performed.
|
|
|
|
The host memory (pointer from MemoryClass::HOST) can be accessed through the
|
|
inline methods: `operator[]()`, `operator*()`, the implicit conversion
|
|
functions `operator T*()`, `operator const T*()`, and the explicit
|
|
conversion template functions `operator U*()`, `operator const U*()` (with
|
|
any suitable type U). In certain cases, using these methods may have
|
|
undefined behavior, e.g. if the host pointer is not currently valid. */
|
|
template <typename T>
|
|
class Memory
|
|
{
|
|
protected:
|
|
friend class MemoryManager;
|
|
friend void MemoryPrintFlags(unsigned flags);
|
|
|
|
enum FlagMask: unsigned
|
|
{
|
|
REGISTERED = 1 << 0, /**< The host pointer is registered with the
|
|
MemoryManager */
|
|
OWNS_HOST = 1 << 1, ///< The host pointer will be deleted by Delete()
|
|
OWNS_DEVICE = 1 << 2, /**< The device pointer will be deleted by
|
|
Delete() */
|
|
OWNS_INTERNAL = 1 << 3, ///< Ownership flag for internal Memory data
|
|
VALID_HOST = 1 << 4, ///< Host pointer is valid
|
|
VALID_DEVICE = 1 << 5, ///< %Device pointer is valid
|
|
USE_DEVICE = 1 << 6, /**< Internal device flag, see e.g.
|
|
Vector::UseDevice() */
|
|
ALIAS = 1 << 7 ///< Pointer is an alias
|
|
};
|
|
|
|
/// Pointer to host memory. Not owned.
|
|
/** The type of the pointer is given by the field #h_mt; it can be any type
|
|
from MemoryClass::HOST. */
|
|
T *h_ptr;
|
|
int capacity; ///< Size of the allocated memory
|
|
MemoryType h_mt; ///< Host memory type
|
|
mutable unsigned flags; ///< Bit flags defined from the #FlagMask enum
|
|
// 'flags' is mutable so that it can be modified in Set{Host,Device}PtrOwner,
|
|
// Copy{From,To}, {ReadWrite,Read,Write}.
|
|
|
|
public:
|
|
/// Default constructor: no initialization.
|
|
Memory() { }
|
|
|
|
/// Copy constructor: default.
|
|
Memory(const Memory &orig) = default;
|
|
|
|
/// Move constructor: default.
|
|
Memory(Memory &&orig) = default;
|
|
|
|
/// Copy-assignment operator: default.
|
|
Memory &operator=(const Memory &orig) = default;
|
|
|
|
/// Move-assignment operator: default.
|
|
Memory &operator=(Memory &&orig) = default;
|
|
|
|
/// Allocate host memory for @a size entries.
|
|
/** The allocation uses the current host memory type returned by
|
|
MemoryManager::GetHostMemoryType(). */
|
|
explicit Memory(int size) { New(size); }
|
|
|
|
/** @brief Allocate memory for @a size entries with the given MemoryType
|
|
@a mt. */
|
|
/** The newly allocated memory is not initialized, however the given
|
|
MemoryType is still set as valid. */
|
|
Memory(int size, MemoryType mt) { New(size, mt); }
|
|
|
|
/** @brief Wrap an externally allocated host pointer, @a ptr with the current
|
|
host memory type returned by MemoryManager::GetHostMemoryType(). */
|
|
/** The parameter @a own determines whether @a ptr will be deleted when the
|
|
method Delete() is called. */
|
|
explicit Memory(T *ptr, int size, bool own) { Wrap(ptr, size, own); }
|
|
|
|
/// Wrap an externally allocated pointer, @a ptr, of the given MemoryType.
|
|
/** The new memory object will have the given MemoryType set as valid.
|
|
|
|
The given @a ptr must be allocated appropriately for the given
|
|
MemoryType.
|
|
|
|
The parameter @a own determines whether @a ptr will be deleted when the
|
|
method Delete() is called. */
|
|
Memory(T *ptr, int size, MemoryType mt, bool own)
|
|
{ Wrap(ptr, size, mt, own); }
|
|
|
|
/** @brief Alias constructor. Create a Memory object that points inside the
|
|
Memory object @a base. */
|
|
/** The new Memory object uses the same MemoryType(s) as @a base. */
|
|
Memory(const Memory &base, int offset, int size)
|
|
{ MakeAlias(base, offset, size); }
|
|
|
|
/// Destructor: default.
|
|
/** @note The destructor will NOT delete the current memory. */
|
|
~Memory() = default;
|
|
|
|
/** @brief Return true if the host pointer is owned. Ownership indicates
|
|
whether the pointer will be deleted by the method Delete(). */
|
|
bool OwnsHostPtr() const { return flags & OWNS_HOST; }
|
|
|
|
/** @brief Set/clear the ownership flag for the host pointer. Ownership
|
|
indicates whether the pointer will be deleted by the method Delete(). */
|
|
void SetHostPtrOwner(bool own) const
|
|
{ flags = own ? (flags | OWNS_HOST) : (flags & ~OWNS_HOST); }
|
|
|
|
/** @brief Return true if the device pointer is owned. Ownership indicates
|
|
whether the pointer will be deleted by the method Delete(). */
|
|
bool OwnsDevicePtr() const { return flags & OWNS_DEVICE; }
|
|
|
|
/** @brief Set/clear the ownership flag for the device pointer. Ownership
|
|
indicates whether the pointer will be deleted by the method Delete(). */
|
|
void SetDevicePtrOwner(bool own) const
|
|
{ flags = own ? (flags | OWNS_DEVICE) : (flags & ~OWNS_DEVICE); }
|
|
|
|
/** @brief Clear the ownership flags for the host and device pointers, as
|
|
well as any internal data allocated by the Memory object. */
|
|
void ClearOwnerFlags() const
|
|
{ flags = flags & ~(OWNS_HOST | OWNS_DEVICE | OWNS_INTERNAL); }
|
|
|
|
/// Read the internal device flag.
|
|
bool UseDevice() const { return flags & USE_DEVICE; }
|
|
|
|
/// Set the internal device flag.
|
|
void UseDevice(bool use_dev) const
|
|
{ flags = use_dev ? (flags | USE_DEVICE) : (flags & ~USE_DEVICE); }
|
|
|
|
/// Return the size of the allocated memory.
|
|
int Capacity() const { return capacity; }
|
|
|
|
/// Reset the memory to be empty, ensuring that Delete() will be a no-op.
|
|
/** This is the Memory class equivalent to setting a pointer to NULL, see
|
|
Empty().
|
|
|
|
@note The current memory is NOT deleted by this method. */
|
|
void Reset();
|
|
|
|
/// Reset the memory and set the host memory type.
|
|
void Reset(MemoryType host_mt);
|
|
|
|
/// Return true if the Memory object is empty, see Reset().
|
|
/** Default-constructed objects are uninitialized, so they are not guaranteed
|
|
to be empty. */
|
|
bool Empty() const { return h_ptr == NULL; }
|
|
|
|
/** @brief Allocate host memory for @a size entries with the current host
|
|
memory type returned by MemoryManager::GetHostMemoryType(). */
|
|
/** @note The current memory is NOT deleted by this method. */
|
|
inline void New(int size);
|
|
|
|
/// Allocate memory for @a size entries with the given MemoryType.
|
|
/** The newly allocated memory is not initialized, however the given
|
|
MemoryType is still set as valid.
|
|
|
|
@note The current memory is NOT deleted by this method. */
|
|
inline void New(int size, MemoryType mt);
|
|
|
|
/** @brief Wrap an externally allocated host pointer, @a ptr with the current
|
|
host memory type returned by MemoryManager::GetHostMemoryType(). */
|
|
/** The parameter @a own determines whether @a ptr will be deleted when the
|
|
method Delete() is called.
|
|
|
|
@note The current memory is NOT deleted by this method. */
|
|
inline void Wrap(T *ptr, int size, bool own);
|
|
|
|
/// Wrap an externally allocated pointer, @a ptr, of the given MemoryType.
|
|
/** The new memory object will have the given MemoryType set as valid.
|
|
|
|
The given @a ptr must be allocated appropriately for the given
|
|
MemoryType.
|
|
|
|
The parameter @a own determines whether @a ptr will be deleted when the
|
|
method Delete() is called.
|
|
|
|
@note The current memory is NOT deleted by this method. */
|
|
inline void Wrap(T *ptr, int size, MemoryType mt, bool own);
|
|
|
|
/** Wrap an externally pair of allocated pointers, @a h_ptr and @ d_ptr,
|
|
of the given host MemoryType @a h_mt. */
|
|
/** The new memory object will have the device MemoryType set as valid.
|
|
|
|
The given @a h_ptr and @a d_ptr must be allocated appropriately for the
|
|
given host MemoryType and its associated device MemoryType:
|
|
- MANAGED => MANAGED,
|
|
- HOST_DEBUG => DEVICE_DEBUG,
|
|
- HOST_UMPIRE => DEVICE_UMPIRE,
|
|
- HOST, HOST_32, HOST_64 => DEVICE.
|
|
|
|
The parameter @a own determines whether both @a h_ptr and @a d_ptr will
|
|
be deleted when the method Delete() is called.
|
|
|
|
@note Ownership can also be controled by using the folowing methods:
|
|
- ClearOwnerFlags,
|
|
- SetHostPtrOwner,
|
|
- SetDevicePtrOwner.
|
|
|
|
@note The current memory is NOT deleted by this method. */
|
|
inline void Wrap(T *h_ptr, T *d_ptr, int size, MemoryType h_mt, bool own);
|
|
|
|
/// Create a memory object that points inside the memory object @a base.
|
|
/** The new Memory object uses the same MemoryType(s) as @a base.
|
|
|
|
@note The current memory is NOT deleted by this method. */
|
|
inline void MakeAlias(const Memory &base, int offset, int size);
|
|
|
|
/** @brief Delete the owned pointers. The Memory is not reset by this method,
|
|
i.e. it will, generally, not be Empty() after this call. */
|
|
inline void Delete();
|
|
|
|
/// Array subscript operator for host memory.
|
|
inline T &operator[](int idx);
|
|
|
|
/// Array subscript operator for host memory, const version.
|
|
inline const T &operator[](int idx) const;
|
|
|
|
/// Direct access to the host memory as T* (implicit conversion).
|
|
/** When the type T is const-qualified, this method can be used only if the
|
|
host pointer is currently valid (the device pointer may be valid or
|
|
invalid).
|
|
|
|
When the type T is not const-qualified, this method can be used only if
|
|
the host pointer is the only valid pointer.
|
|
|
|
When the Memory is empty, this method can be used and it returns NULL. */
|
|
inline operator T*();
|
|
|
|
/// Direct access to the host memory as const T* (implicit conversion).
|
|
/** This method can be used only if the host pointer is currently valid (the
|
|
device pointer may be valid or invalid).
|
|
|
|
When the Memory is empty, this method can be used and it returns NULL. */
|
|
inline operator const T*() const;
|
|
|
|
/// Direct access to the host memory via explicit typecast.
|
|
/** A pointer to type T must be reinterpret_cast-able to a pointer to type U.
|
|
In particular, this method cannot be used to cast away const-ness from
|
|
the base type T.
|
|
|
|
When the type U is const-qualified, this method can be used only if the
|
|
host pointer is currently valid (the device pointer may be valid or
|
|
invalid).
|
|
|
|
When the type U is not const-qualified, this method can be used only if
|
|
the host pointer is the only valid pointer.
|
|
|
|
When the Memory is empty, this method can be used and it returns NULL. */
|
|
template <typename U>
|
|
inline explicit operator U*();
|
|
|
|
/// Direct access to the host memory via explicit typecast, const version.
|
|
/** A pointer to type T must be reinterpret_cast-able to a pointer to type
|
|
const U.
|
|
|
|
This method can be used only if the host pointer is currently valid (the
|
|
device pointer may be valid or invalid).
|
|
|
|
When the Memory is empty, this method can be used and it returns NULL. */
|
|
template <typename U>
|
|
inline explicit operator const U*() const;
|
|
|
|
/// Get read-write access to the memory with the given MemoryClass.
|
|
/** If only read or only write access is needed, then the methods
|
|
Read() or Write() should be used instead of this method.
|
|
|
|
The parameter @a size must not exceed the Capacity(). */
|
|
inline T *ReadWrite(MemoryClass mc, int size);
|
|
|
|
/// Get read-only access to the memory with the given MemoryClass.
|
|
/** The parameter @a size must not exceed the Capacity(). */
|
|
inline const T *Read(MemoryClass mc, int size) const;
|
|
|
|
/// Get write-only access to the memory with the given MemoryClass.
|
|
/** The parameter @a size must not exceed the Capacity().
|
|
|
|
The contents of the returned pointer is undefined, unless it was
|
|
validated by a previous call to Read() or ReadWrite() with
|
|
the same MemoryClass. */
|
|
inline T *Write(MemoryClass mc, int size);
|
|
|
|
/// Copy the host/device pointer validity flags from @a other to @a *this.
|
|
/** This method synchronizes the pointer validity flags of two Memory objects
|
|
that use the same host/device pointers, or when @a *this is an alias
|
|
(sub-Memory) of @a other. Typically, this method should be called after
|
|
@a other is manipulated in a way that changes its pointer validity flags
|
|
(e.g. it was moved from device to host memory). */
|
|
inline void Sync(const Memory &other) const;
|
|
|
|
/** @brief Update the alias Memory @a *this to match the memory location (all
|
|
valid locations) of its base Memory, @a base. */
|
|
/** This method is useful when alias Memory is moved and manipulated in a
|
|
different memory space. Such operations render the pointer validity flags
|
|
of the base incorrect. Calling this method will ensure that @a base is
|
|
up-to-date. Note that this is achieved by moving/copying @a *this (if
|
|
necessary), and not @a base. */
|
|
inline void SyncAlias(const Memory &base, int alias_size) const;
|
|
|
|
/** @brief Return a MemoryType that is currently valid. If both the host and
|
|
the device pointers are currently valid, then the device memory type is
|
|
returned. */
|
|
inline MemoryType GetMemoryType() const;
|
|
|
|
/// Copy @a size entries from @a src to @a *this.
|
|
/** The given @a size should not exceed the Capacity() of the source @a src
|
|
and the destination, @a *this. */
|
|
inline void CopyFrom(const Memory &src, int size);
|
|
|
|
/// Copy @a size entries from the host pointer @a src to @a *this.
|
|
/** The given @a size should not exceed the Capacity() of @a *this. */
|
|
inline void CopyFromHost(const T *src, int size);
|
|
|
|
/// Copy @a size entries from @a *this to @a dest.
|
|
/** The given @a size should not exceed the Capacity() of @a *this and the
|
|
destination, @a dest. */
|
|
inline void CopyTo(Memory &dest, int size) const
|
|
{ dest.CopyFrom(*this, size); }
|
|
|
|
/// Copy @a size entries from @a *this to the host pointer @a dest.
|
|
/** The given @a size should not exceed the Capacity() of @a *this. */
|
|
inline void CopyToHost(T *dest, int size) const;
|
|
|
|
/// Print the internal flags.
|
|
/** This method can be useful for debugging. It is explicitly instantiated
|
|
for Memory<T> with T = int and T = double. */
|
|
inline void PrintFlags() const;
|
|
|
|
/// If both the host and the device data are valid, compare their contents.
|
|
/** This method can be useful for debugging. It is explicitly instantiated
|
|
for Memory<T> with T = int and T = double. */
|
|
inline int CompareHostAndDevice(int size) const;
|
|
|
|
private:
|
|
// GCC 4.8 workaround: max_align_t is not in std.
|
|
static constexpr std::size_t def_align_bytes_()
|
|
{
|
|
using namespace std;
|
|
return alignof(max_align_t);
|
|
}
|
|
static constexpr std::size_t def_align_bytes = def_align_bytes_();
|
|
static constexpr std::size_t new_align_bytes =
|
|
alignof(T) > def_align_bytes ? alignof(T) : def_align_bytes;
|
|
|
|
template <std::size_t align_bytes, bool dummy = true> struct Alloc
|
|
{
|
|
static inline T *New(std::size_t)
|
|
{
|
|
#if __cplusplus < 201703L
|
|
// Generate an error in debug mode
|
|
MFEM_ASSERT(false, "overaligned type cannot use MemoryType::HOST");
|
|
return nullptr;
|
|
#else
|
|
return new T[size];
|
|
#endif
|
|
}
|
|
};
|
|
|
|
#if __cplusplus < 201703L
|
|
template<bool dummy> struct Alloc<def_align_bytes,dummy>
|
|
{
|
|
static inline T *New(std::size_t size) { return new T[size]; }
|
|
};
|
|
#endif
|
|
};
|
|
|
|
|
|
/** The MFEM memory manager class. Host-side pointers are inserted into this
|
|
manager which keeps track of the associated device pointer, and where the
|
|
data currently resides. */
|
|
class MemoryManager
|
|
{
|
|
private:
|
|
|
|
typedef MemoryType MemType;
|
|
typedef Memory<int> Mem;
|
|
|
|
template <typename T> friend class Memory;
|
|
|
|
/// Host memory type set during the Setup.
|
|
static MemoryType host_mem_type;
|
|
|
|
/// Device memory type set during the Setup.
|
|
static MemoryType device_mem_type;
|
|
|
|
/// Allow to detect if a global memory manager instance exists.
|
|
static bool exists;
|
|
|
|
/// Return true if the global memory manager instance exists.
|
|
static bool Exists() { return exists; }
|
|
|
|
/// Host and device allocator names for Umpire.
|
|
#ifdef MFEM_USE_UMPIRE
|
|
static const char *h_umpire_name;
|
|
static const char *d_umpire_name;
|
|
#endif
|
|
|
|
private: // Static methods used by the Memory<T> class
|
|
|
|
/// Allocate and register a new pointer. Return the host pointer.
|
|
/// h_tmp must be already allocated using new T[] if mt is a pure device
|
|
/// memory type, e.g. CUDA (mt will not be HOST).
|
|
static void *New_(void *h_tmp, size_t bytes, MemoryType mt, unsigned &flags);
|
|
|
|
/// Register an external pointer of the given MemoryType.
|
|
/// Return the host pointer.
|
|
static void *Register_(void *ptr, void *h_ptr, size_t bytes, MemoryType mt,
|
|
bool own, bool alias, unsigned &flags);
|
|
|
|
/// Register an alias. Note: base_h_ptr may be an alias.
|
|
static void Alias_(void *base_h_ptr, size_t offset, size_t bytes,
|
|
unsigned base_flags, unsigned &flags);
|
|
|
|
/// Un-register and free memory identified by its host pointer. Returns the
|
|
/// memory type of the host pointer.
|
|
static MemoryType Delete_(void *h_ptr, MemoryType mt, unsigned flags);
|
|
|
|
/// Check if the memory types given the memory class are valid
|
|
static bool MemoryClassCheck_(MemoryClass mc, void *h_ptr,
|
|
MemoryType h_mt, size_t bytes, unsigned flags);
|
|
|
|
/// Return the dual memory type of the given one.
|
|
static MemoryType GetDualMemoryType_(MemoryType mt);
|
|
|
|
/// Return a pointer to the memory identified by the host pointer h_ptr for
|
|
/// access with the given MemoryClass.
|
|
static void *ReadWrite_(void *h_ptr, MemoryType h_mt, MemoryClass mc,
|
|
size_t bytes, unsigned &flags);
|
|
|
|
static const void *Read_(void *h_ptr, MemoryType h_mt, MemoryClass mc,
|
|
size_t bytes, unsigned &flags);
|
|
|
|
static void *Write_(void *h_ptr, MemoryType h_mt, MemoryClass mc,
|
|
size_t bytes, unsigned &flags);
|
|
|
|
static void SyncAlias_(const void *base_h_ptr, void *alias_h_ptr,
|
|
size_t alias_bytes, unsigned base_flags,
|
|
unsigned &alias_flags);
|
|
|
|
/// Return the type the of the currently valid memory.
|
|
/// If more than one types are valid, return a device type.
|
|
static MemoryType GetDeviceMemoryType_(void *h_ptr);
|
|
|
|
/// Return the type the of the host memory.
|
|
static MemoryType GetHostMemoryType_(void *h_ptr);
|
|
|
|
/// Verify that h_mt and h_ptr's h_mt (memory or alias) are equal.
|
|
static void CheckHostMemoryType_(MemoryType h_mt, void *h_ptr);
|
|
|
|
/// Copy entries from valid memory type to valid memory type.
|
|
/// Both dest_h_ptr and src_h_ptr are registered host pointers.
|
|
static void Copy_(void *dest_h_ptr, const void *src_h_ptr, size_t bytes,
|
|
unsigned src_flags, unsigned &dest_flags);
|
|
|
|
/// Copy entries from valid memory type to host memory, where dest_h_ptr is
|
|
/// not a registered host pointer and src_h_ptr is a registered host pointer.
|
|
static void CopyToHost_(void *dest_h_ptr, const void *src_h_ptr,
|
|
size_t bytes, unsigned src_flags);
|
|
|
|
/// Copy entries from host memory to valid memory type, where dest_h_ptr is a
|
|
/// registered host pointer and src_h_ptr is not a registered host pointer.
|
|
static void CopyFromHost_(void *dest_h_ptr, const void *src_h_ptr,
|
|
size_t bytes, unsigned &dest_flags);
|
|
|
|
/// Check if the host pointer has been registered in the memory manager.
|
|
static bool IsKnown_(const void *h_ptr);
|
|
|
|
/** @brief Check if the host pointer has been registered as an alias in the
|
|
memory manager. */
|
|
static bool IsAlias_(const void *h_ptr);
|
|
|
|
/// Compare the contents of the host and the device memory.
|
|
static int CompareHostAndDevice_(void *h_ptr, size_t size, unsigned flags);
|
|
|
|
private:
|
|
|
|
/// Insert a host address @a h_ptr and size *a bytes in the memory map to be
|
|
/// managed.
|
|
void Insert(void *h_ptr, size_t bytes, MemoryType h_mt, MemoryType d_mt);
|
|
|
|
/// Insert a device and the host addresses in the memory map
|
|
void InsertDevice(void *d_ptr, void *h_ptr, size_t bytes,
|
|
MemoryType h_mt, MemoryType d_mt);
|
|
|
|
/// Insert an alias in the alias map
|
|
void InsertAlias(const void *base_ptr, void *alias_ptr,
|
|
const size_t bytes, const bool base_is_alias);
|
|
|
|
/// Erase an address from the memory map, as well as all its aliases
|
|
void Erase(void *h_ptr, bool free_dev_ptr = true);
|
|
|
|
/// Erase an alias from the aliases map
|
|
void EraseAlias(void *alias_ptr);
|
|
|
|
/// Return the corresponding device pointer of h_ptr,
|
|
/// allocating and moving the data if needed
|
|
void *GetDevicePtr(const void *h_ptr, size_t bytes, bool copy_data);
|
|
|
|
/// Return the corresponding device pointer of alias_ptr,
|
|
/// allocating and moving the data if needed
|
|
void *GetAliasDevicePtr(const void *alias_ptr, size_t bytes, bool copy_data);
|
|
|
|
/// Return the corresponding host pointer of d_ptr,
|
|
/// allocating and moving the data if needed
|
|
void *GetHostPtr(const void *d_ptr, size_t bytes, bool copy_data);
|
|
|
|
/// Return the corresponding host pointer of alias_ptr,
|
|
/// allocating and moving the data if needed
|
|
void *GetAliasHostPtr(const void *alias_ptr, size_t bytes, bool copy_data);
|
|
|
|
public:
|
|
MemoryManager();
|
|
~MemoryManager();
|
|
|
|
/// Initialize the memory manager.
|
|
void Init();
|
|
|
|
/// Configure the Memory manager with given default host and device types
|
|
/// This method will be called when configuring a device.
|
|
void Configure(const MemoryType h_mt, const MemoryType d_mt);
|
|
|
|
#ifdef MFEM_USE_UMPIRE
|
|
/// Set the host and device UMpire allocator names
|
|
void SetUmpireAllocatorNames(const char *h_name, const char *d_name);
|
|
const char *GetUmpireAllocatorHostName() { return h_umpire_name; }
|
|
const char *GetUmpireAllocatorDeviceName() { return d_umpire_name; }
|
|
#endif
|
|
|
|
/// Free all the device memories
|
|
void Destroy();
|
|
|
|
/// Return true if the pointer is known by the memory manager
|
|
bool IsKnown(const void *h_ptr) { return IsKnown_(h_ptr); }
|
|
|
|
/// Return true if the pointer is known by the memory manager as an alias
|
|
bool IsAlias(const void *h_ptr) { return IsAlias_(h_ptr); }
|
|
|
|
/// Check if the host pointer has been registered in the memory manager
|
|
void RegisterCheck(void *h_ptr);
|
|
|
|
/// Prints all pointers known by the memory manager,
|
|
/// returning the number of printed pointers
|
|
int PrintPtrs(std::ostream &out = mfem::out);
|
|
|
|
/// Prints all aliases known by the memory manager
|
|
/// returning the number of printed pointers
|
|
int PrintAliases(std::ostream &out = mfem::out);
|
|
|
|
static MemoryType GetHostMemoryType() { return host_mem_type; }
|
|
static MemoryType GetDeviceMemoryType() { return device_mem_type; }
|
|
};
|
|
|
|
|
|
// Inline methods
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::Reset()
|
|
{
|
|
h_ptr = NULL;
|
|
h_mt = MemoryManager::host_mem_type;
|
|
capacity = 0;
|
|
flags = 0;
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::Reset(MemoryType host_mt)
|
|
{
|
|
h_ptr = NULL;
|
|
h_mt = host_mt;
|
|
capacity = 0;
|
|
flags = 0;
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::New(int size)
|
|
{
|
|
capacity = size;
|
|
flags = OWNS_HOST | VALID_HOST;
|
|
h_mt = MemoryManager::host_mem_type;
|
|
h_ptr = (h_mt == MemoryType::HOST) ? Alloc<new_align_bytes>::New(size) :
|
|
(T*)MemoryManager::New_(nullptr, size*sizeof(T), h_mt, flags);
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::New(int size, MemoryType mt)
|
|
{
|
|
capacity = size;
|
|
const size_t bytes = size*sizeof(T);
|
|
const bool mt_host = mt == MemoryType::HOST;
|
|
if (mt_host) { flags = OWNS_HOST | VALID_HOST; }
|
|
h_mt = IsHostMemory(mt) ? mt : MemoryManager::GetDualMemoryType_(mt);
|
|
T *h_tmp = (h_mt == MemoryType::HOST) ?
|
|
Alloc<new_align_bytes>::New(size) : nullptr;
|
|
h_ptr = (mt_host) ? h_tmp : (T*)MemoryManager::New_(h_tmp, bytes, mt, flags);
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::Wrap(T *ptr, int size, bool own)
|
|
{
|
|
h_ptr = ptr;
|
|
capacity = size;
|
|
const size_t bytes = size*sizeof(T);
|
|
flags = (own ? OWNS_HOST : 0) | VALID_HOST;
|
|
h_mt = MemoryManager::host_mem_type;
|
|
#ifdef MFEM_DEBUG
|
|
if (own && MemoryManager::Exists())
|
|
{ MFEM_VERIFY(h_mt == MemoryManager::GetHostMemoryType_(h_ptr),""); }
|
|
#endif
|
|
if (own && h_mt != MemoryType::HOST)
|
|
{ MemoryManager::Register_(ptr, ptr, bytes, h_mt, own, false, flags); }
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::Wrap(T *ptr, int size, MemoryType mt, bool own)
|
|
{
|
|
capacity = size;
|
|
if (IsHostMemory(mt))
|
|
{
|
|
h_mt = mt;
|
|
h_ptr = ptr;
|
|
if (mt == MemoryType::HOST || !own)
|
|
{
|
|
// Skip restration
|
|
flags = (own ? OWNS_HOST : 0) | VALID_HOST;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
h_mt = MemoryManager::GetDualMemoryType_(mt);
|
|
h_ptr = (h_mt == MemoryType::HOST) ? new T[size] : nullptr;
|
|
}
|
|
flags = 0;
|
|
h_ptr = (T*)MemoryManager::Register_(ptr, h_ptr, size*sizeof(T), mt,
|
|
own, false, flags);
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::Wrap(T *ptr, T *d_ptr, int size, MemoryType mt, bool own)
|
|
{
|
|
h_mt = mt;
|
|
flags = 0;
|
|
h_ptr = ptr;
|
|
capacity = size;
|
|
MFEM_ASSERT(IsHostMemory(h_mt),"");
|
|
const size_t bytes = size*sizeof(T);
|
|
const MemoryType d_mt = MemoryManager::GetDualMemoryType_(h_mt);
|
|
MemoryManager::Register_(d_ptr, h_ptr, bytes, d_mt, own, false, flags);
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::MakeAlias(const Memory &base, int offset, int size)
|
|
{
|
|
capacity = size;
|
|
h_mt = base.h_mt;
|
|
h_ptr = base.h_ptr + offset;
|
|
if (!(base.flags & REGISTERED))
|
|
{ flags = (base.flags | ALIAS) & ~(OWNS_HOST | OWNS_DEVICE); }
|
|
else
|
|
{
|
|
const size_t s_bytes = size*sizeof(T);
|
|
const size_t o_bytes = offset*sizeof(T);
|
|
MemoryManager::Alias_(base.h_ptr, o_bytes, s_bytes, base.flags, flags);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::Delete()
|
|
{
|
|
const bool registered = flags & REGISTERED;
|
|
const bool mt_host = h_mt == MemoryType::HOST;
|
|
const bool std_delete = !registered && mt_host;
|
|
|
|
if (std_delete ||
|
|
MemoryManager::Delete_((void*)h_ptr, h_mt, flags) == MemoryType::HOST)
|
|
{
|
|
if (flags & OWNS_HOST) { delete [] h_ptr; }
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
inline T &Memory<T>::operator[](int idx)
|
|
{
|
|
MFEM_ASSERT((flags & VALID_HOST) && !(flags & VALID_DEVICE),
|
|
"invalid host pointer access");
|
|
return h_ptr[idx];
|
|
}
|
|
|
|
template <typename T>
|
|
inline const T &Memory<T>::operator[](int idx) const
|
|
{
|
|
MFEM_ASSERT((flags & VALID_HOST), "invalid host pointer access");
|
|
return h_ptr[idx];
|
|
}
|
|
|
|
template <typename T>
|
|
inline Memory<T>::operator T*()
|
|
{
|
|
MFEM_ASSERT(Empty() ||
|
|
((flags & VALID_HOST) &&
|
|
(std::is_const<T>::value || !(flags & VALID_DEVICE))),
|
|
"invalid host pointer access");
|
|
return h_ptr;
|
|
}
|
|
|
|
template <typename T>
|
|
inline Memory<T>::operator const T*() const
|
|
{
|
|
MFEM_ASSERT(Empty() || (flags & VALID_HOST), "invalid host pointer access");
|
|
return h_ptr;
|
|
}
|
|
|
|
template <typename T> template <typename U>
|
|
inline Memory<T>::operator U*()
|
|
{
|
|
MFEM_ASSERT(Empty() ||
|
|
((flags & VALID_HOST) &&
|
|
(std::is_const<U>::value || !(flags & VALID_DEVICE))),
|
|
"invalid host pointer access");
|
|
return reinterpret_cast<U*>(h_ptr);
|
|
}
|
|
|
|
template <typename T> template <typename U>
|
|
inline Memory<T>::operator const U*() const
|
|
{
|
|
MFEM_ASSERT(Empty() || (flags & VALID_HOST), "invalid host pointer access");
|
|
return reinterpret_cast<U*>(h_ptr);
|
|
}
|
|
|
|
template <typename T>
|
|
inline T *Memory<T>::ReadWrite(MemoryClass mc, int size)
|
|
{
|
|
const size_t bytes = size * sizeof(T);
|
|
if (!(flags & REGISTERED))
|
|
{
|
|
if (mc == MemoryClass::HOST) { return h_ptr; }
|
|
MemoryManager::Register_(h_ptr, nullptr, capacity*sizeof(T), h_mt,
|
|
flags & OWNS_HOST, flags & ALIAS, flags);
|
|
}
|
|
return (T*)MemoryManager::ReadWrite_(h_ptr, h_mt, mc, bytes, flags);
|
|
}
|
|
|
|
template <typename T>
|
|
inline const T *Memory<T>::Read(MemoryClass mc, int size) const
|
|
{
|
|
const size_t bytes = size * sizeof(T);
|
|
if (!(flags & REGISTERED))
|
|
{
|
|
if (mc == MemoryClass::HOST) { return h_ptr; }
|
|
MemoryManager::Register_(h_ptr, nullptr, capacity*sizeof(T), h_mt,
|
|
flags & OWNS_HOST, flags & ALIAS, flags);
|
|
}
|
|
return (const T*)MemoryManager::Read_(h_ptr, h_mt, mc, bytes, flags);
|
|
}
|
|
|
|
template <typename T>
|
|
inline T *Memory<T>::Write(MemoryClass mc, int size)
|
|
{
|
|
const size_t bytes = size * sizeof(T);
|
|
if (!(flags & REGISTERED))
|
|
{
|
|
if (mc == MemoryClass::HOST) { return h_ptr; }
|
|
MemoryManager::Register_(h_ptr, nullptr, capacity*sizeof(T), h_mt,
|
|
flags & OWNS_HOST, flags & ALIAS, flags);
|
|
}
|
|
return (T*)MemoryManager::Write_(h_ptr, h_mt, mc, bytes, flags);
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::Sync(const Memory &other) const
|
|
{
|
|
if (!(flags & REGISTERED) && (other.flags & REGISTERED))
|
|
{
|
|
MFEM_ASSERT(h_ptr == other.h_ptr &&
|
|
(flags & ALIAS) == (other.flags & ALIAS),
|
|
"invalid input");
|
|
flags = (flags | REGISTERED) & ~(OWNS_DEVICE | OWNS_INTERNAL);
|
|
}
|
|
flags = (flags & ~(VALID_HOST | VALID_DEVICE)) |
|
|
(other.flags & (VALID_HOST | VALID_DEVICE));
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::SyncAlias(const Memory &base, int alias_size) const
|
|
{
|
|
// Assuming that if *this is registered then base is also registered.
|
|
MFEM_ASSERT(!(flags & REGISTERED) || (base.flags & REGISTERED),
|
|
"invalid base state");
|
|
if (!(base.flags & REGISTERED)) { return; }
|
|
MemoryManager::SyncAlias_(base.h_ptr, h_ptr, alias_size*sizeof(T),
|
|
base.flags, flags);
|
|
}
|
|
|
|
template <typename T>
|
|
inline MemoryType Memory<T>::GetMemoryType() const
|
|
{
|
|
if (!(flags & VALID_DEVICE)) { return h_mt; }
|
|
return MemoryManager::GetDeviceMemoryType_(h_ptr);
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::CopyFrom(const Memory &src, int size)
|
|
{
|
|
if (!(flags & REGISTERED) && !(src.flags & REGISTERED))
|
|
{
|
|
if (h_ptr != src.h_ptr && size != 0)
|
|
{
|
|
MFEM_ASSERT(h_ptr + size <= src || src + size <= h_ptr,
|
|
"data overlaps!");
|
|
std::memcpy(h_ptr, src, size*sizeof(T));
|
|
}
|
|
// *this is not registered, so (flags & VALID_HOST) must be true
|
|
}
|
|
else
|
|
{
|
|
MemoryManager::Copy_(h_ptr, src.h_ptr, size*sizeof(T), src.flags, flags);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::CopyFromHost(const T *src, int size)
|
|
{
|
|
if (!(flags & REGISTERED))
|
|
{
|
|
if (h_ptr != src && size != 0)
|
|
{
|
|
MFEM_ASSERT(h_ptr + size <= src || src + size <= h_ptr,
|
|
"data overlaps!");
|
|
std::memcpy(h_ptr, src, size*sizeof(T));
|
|
}
|
|
// *this is not registered, so (flags & VALID_HOST) must be true
|
|
}
|
|
else
|
|
{
|
|
MemoryManager::CopyFromHost_(h_ptr, src, size*sizeof(T), flags);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::CopyToHost(T *dest, int size) const
|
|
{
|
|
if (!(flags & REGISTERED))
|
|
{
|
|
if (h_ptr != dest && size != 0)
|
|
{
|
|
MFEM_ASSERT(h_ptr + size <= dest || dest + size <= h_ptr,
|
|
"data overlaps!");
|
|
std::memcpy(dest, h_ptr, size*sizeof(T));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MemoryManager::CopyToHost_(dest, h_ptr, size*sizeof(T), flags);
|
|
}
|
|
}
|
|
|
|
|
|
/** @brief Print the state of a Memory object based on its internal flags.
|
|
Useful in a debugger. See also Memory<T>::PrintFlags(). */
|
|
extern void MemoryPrintFlags(unsigned flags);
|
|
|
|
|
|
template <typename T>
|
|
inline void Memory<T>::PrintFlags() const
|
|
{
|
|
MemoryPrintFlags(flags);
|
|
}
|
|
|
|
template <typename T>
|
|
inline int Memory<T>::CompareHostAndDevice(int size) const
|
|
{
|
|
if (!(flags & VALID_HOST) || !(flags & VALID_DEVICE)) { return 0; }
|
|
return MemoryManager::CompareHostAndDevice_(h_ptr, size*sizeof(T), flags);
|
|
}
|
|
|
|
|
|
/// The (single) global memory manager object
|
|
extern MemoryManager mm;
|
|
|
|
} // namespace mfem
|
|
|
|
#endif // MFEM_MEM_MANAGER_HPP
|