Resources¶
Each computer system will have a number of distinct places in which the system will allow you to allocate memory. In Umpire’s world, these are memory resources. A memory resource can correspond to a hardware resource, but can also be used to identify memory with a particular characteristic, like “pinned” memory in a GPU system.
When you configure Umpire, it will create
umpire::resource::MemoryResource
s according to what is available on
the system you are building for. For each resource, Umpire will create a
default umpire::Allocator
that you can use. In the previous example,
we were actually using an umpire::Allocator
created for the memory
resource corresponding to the CPU memory
The easiest way to identify resources is by name. The “HOST” resource is always available. In a modern NVIDIA GPU system, we also have resources that represent global GPU memory (“DEVICE”), unified memory that can be accessed by the CPU or GPU (“UM”) and host memory that can be accessed by the GPU (“PINNED”);
Umpire will create an umpire::Allocator
for each of these resources,
and you can get them using the same
umpire::ResourceManager::getAllocator()
call you saw in the previous
example:
auto& rm = umpire::ResourceManager::getInstance();
umpire::Allocator allocator = rm.getAllocator(resource);
Note that every allocator supports the same calls, no matter which resource it is for, this means we can run the same code for all the resources available in the system:
void allocate_and_deallocate(const std::string& resource)
{
constexpr size_t SIZE = 1024;
auto& rm = umpire::ResourceManager::getInstance();
umpire::Allocator allocator = rm.getAllocator(resource);
double* data = static_cast<double*>(
allocator.allocate(SIZE*sizeof(double)));
std::cout << "Allocated " << (SIZE*sizeof(double)) << " bytes using the "
<< allocator.getName() << " allocator...";
allocator.deallocate(data);
std::cout << " deallocated." << std::endl;
}
As you can see, we can call this function with any valid resource name:
allocate_and_deallocate("HOST");
#if defined(UMPIRE_ENABLE_CUDA)
allocate_and_deallocate("DEVICE");
allocate_and_deallocate("UM");
allocate_and_deallocate("PINNED");
#endif
In the next example, we will learn how to move data between resources using operations.
//////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2019, Lawrence Livermore National Security, LLC.
// Produced at the Lawrence Livermore National Laboratory
//
// Created by David Beckingsale, david@llnl.gov
// LLNL-CODE-747640
//
// All rights reserved.
//
// This file is part of Umpire.
//
// For details, see https://github.com/LLNL/Umpire
// Please also see the LICENSE file for MIT license.
//////////////////////////////////////////////////////////////////////////////
#include "umpire/Allocator.hpp"
#include "umpire/ResourceManager.hpp"
void allocate_and_deallocate(const std::string& resource)
{
constexpr size_t SIZE = 1024;
auto& rm = umpire::ResourceManager::getInstance();
umpire::Allocator allocator = rm.getAllocator(resource);
double* data = static_cast<double*>(
allocator.allocate(SIZE*sizeof(double)));
std::cout << "Allocated " << (SIZE*sizeof(double)) << " bytes using the "
<< allocator.getName() << " allocator...";
allocator.deallocate(data);
std::cout << " deallocated." << std::endl;
}
int main(int, char**) {
allocate_and_deallocate("HOST");
#if defined(UMPIRE_ENABLE_CUDA)
allocate_and_deallocate("DEVICE");
allocate_and_deallocate("UM");
allocate_and_deallocate("PINNED");
#endif
return 0;
}