Operations¶
Moving and modifying data in a heterogenous memory system can be annoying. You have to keep track of the source and destination, and often use vendor-specific APIs to perform the modifications. In Umpire, all data modification and movement is wrapped up in a concept we call operations. Full documentation for all of these is available here. The full code listing for each example is include at the bottom of the page.
Copy¶
Let’s start by looking at how we copy data around. The
umpire::ResourceManager
provides an interface to copy that handles
figuring out where the source and destination pointers were allocated, and
selects the correct implementation to copy the data:
rm.copy(dest_data, source_data);
This example allocates the destination data using any valid Allocator.
Move¶
If you want to move data to a new Allocator and deallocate the old copy, Umpire
provides a umpire::ResourceManager::move()
operation.
double* dest_data =
static_cast<double*>(rm.move(source_data, dest_allocator));
The move operation combines an allocation, a copy, and a deallocate into one function call, allowing you to move data without having to have the destination data allocated. As always, this operation will work with any valid destination Allocator.
Memset¶
Setting a whole block of memory to a value (like 0) is a common operation, that
most people know as a memset. Umpire provides a
umpire::ResourceManager::memset()
implementation that can be applied to
any allocation, regardless of where it came from:
rm.memset(data, 0);
Reallocate¶
Reallocating CPU memory is easy, there is a function designed specifically to
do it: realloc
. When the original allocation was made in a different memory
however, you can be out of luck. Umpire provides a
umpire::ResourceManager::reallocate()
operation:
data = static_cast<double*>(rm.reallocate(data, REALLOCATED_SIZE));
This method returns a pointer to the reallocated data. Like all operations, this can be used regardless of the Allocator used for the source data.
Listings¶
Copy Example Listing
//////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016-20, Lawrence Livermore National Security, LLC and Umpire
// project contributors. See the COPYRIGHT file for details.
//
// SPDX-License-Identifier: (MIT)
//////////////////////////////////////////////////////////////////////////////
#include "umpire/Allocator.hpp"
#include "umpire/ResourceManager.hpp"
void copy_data(double* source_data, std::size_t size,
const std::string& destination)
{
auto& rm = umpire::ResourceManager::getInstance();
auto dest_allocator = rm.getAllocator(destination);
double* dest_data =
static_cast<double*>(dest_allocator.allocate(size * sizeof(double)));
// _sphinx_tag_tut_copy_start
rm.copy(dest_data, source_data);
// _sphinx_tag_tut_copy_end
std::cout << "Copied source data (" << source_data << ") to destination "
<< destination << " (" << dest_data << ")" << std::endl;
dest_allocator.deallocate(dest_data);
}
int main(int, char**)
{
constexpr std::size_t SIZE = 1024;
auto& rm = umpire::ResourceManager::getInstance();
auto allocator = rm.getAllocator("HOST");
double* data =
static_cast<double*>(allocator.allocate(SIZE * sizeof(double)));
std::cout << "Allocated " << (SIZE * sizeof(double)) << " bytes using the "
<< allocator.getName() << " allocator." << std::endl;
std::cout << "Filling with 0.0...";
for (std::size_t i = 0; i < SIZE; i++) {
data[i] = 0.0;
}
std::cout << "done." << std::endl;
copy_data(data, SIZE, "HOST");
#if defined(UMPIRE_ENABLE_DEVICE)
copy_data(data, SIZE, "DEVICE");
#endif
#if defined(UMPIRE_ENABLE_UM)
copy_data(data, SIZE, "UM");
#endif
#if defined(UMPIRE_ENABLE_PINNED)
copy_data(data, SIZE, "PINNED");
#endif
allocator.deallocate(data);
return 0;
}
Move Example Listing
//////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016-20, Lawrence Livermore National Security, LLC and Umpire
// project contributors. See the COPYRIGHT file for details.
//
// SPDX-License-Identifier: (MIT)
//////////////////////////////////////////////////////////////////////////////
#include "umpire/Allocator.hpp"
#include "umpire/ResourceManager.hpp"
double* move_data(double* source_data, const std::string& destination)
{
auto& rm = umpire::ResourceManager::getInstance();
auto dest_allocator = rm.getAllocator(destination);
std::cout << "Moved source data (" << source_data << ") to destination ";
// _sphinx_tag_tut_move_start
double* dest_data =
static_cast<double*>(rm.move(source_data, dest_allocator));
// _sphinx_tag_tut_move_end
std::cout << destination << " (" << dest_data << ")" << std::endl;
return dest_data;
}
int main(int, char**)
{
constexpr std::size_t SIZE = 1024;
auto& rm = umpire::ResourceManager::getInstance();
auto allocator = rm.getAllocator("HOST");
double* data =
static_cast<double*>(allocator.allocate(SIZE * sizeof(double)));
std::cout << "Allocated " << (SIZE * sizeof(double)) << " bytes using the "
<< allocator.getName() << " allocator." << std::endl;
std::cout << "Filling with 0.0...";
for (std::size_t i = 0; i < SIZE; i++) {
data[i] = 0.0;
}
std::cout << "done." << std::endl;
data = move_data(data, "HOST");
#if defined(UMPIRE_ENABLE_DEVICE)
data = move_data(data, "DEVICE");
#endif
#if defined(UMPIRE_ENABLE_UM)
data = move_data(data, "UM");
#endif
#if defined(UMPIRE_ENABLE_PINNED)
data = move_data(data, "PINNED");
#endif
rm.deallocate(data);
return 0;
}
Memset Example Listing
//////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016-20, Lawrence Livermore National Security, LLC and Umpire
// project contributors. See the COPYRIGHT file for details.
//
// SPDX-License-Identifier: (MIT)
//////////////////////////////////////////////////////////////////////////////
#include "umpire/Allocator.hpp"
#include "umpire/ResourceManager.hpp"
int main(int, char**)
{
constexpr std::size_t SIZE = 1024;
auto& rm = umpire::ResourceManager::getInstance();
const std::string destinations[] = {
"HOST"
#if defined(UMPIRE_ENABLE_DEVICE)
,
"DEVICE"
#endif
#if defined(UMPIRE_ENABLE_UM)
,
"UM"
#endif
#if defined(UMPIRE_ENABLE_PINNED)
,
"PINNED"
#endif
};
for (auto& destination : destinations) {
auto allocator = rm.getAllocator(destination);
double* data =
static_cast<double*>(allocator.allocate(SIZE * sizeof(double)));
std::cout << "Allocated " << (SIZE * sizeof(double)) << " bytes using the "
<< allocator.getName() << " allocator." << std::endl;
// _sphinx_tag_tut_memset_start
rm.memset(data, 0);
// _sphinx_tag_tut_memset_end
std::cout << "Set data from " << destination << " (" << data << ") to 0."
<< std::endl;
allocator.deallocate(data);
}
return 0;
}
Reallocate Example Listing
//////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016-20, Lawrence Livermore National Security, LLC and Umpire
// project contributors. See the COPYRIGHT file for details.
//
// SPDX-License-Identifier: (MIT)
//////////////////////////////////////////////////////////////////////////////
#include "umpire/Allocator.hpp"
#include "umpire/ResourceManager.hpp"
int main(int, char**)
{
constexpr std::size_t SIZE = 1024;
constexpr std::size_t REALLOCATED_SIZE = 256;
auto& rm = umpire::ResourceManager::getInstance();
const std::string destinations[] = {
"HOST"
#if defined(UMPIRE_ENABLE_DEVICE)
,
"DEVICE"
#endif
#if defined(UMPIRE_ENABLE_UM)
,
"UM"
#endif
#if defined(UMPIRE_ENABLE_PINNED)
,
"PINNED"
#endif
};
for (auto& destination : destinations) {
auto allocator = rm.getAllocator(destination);
double* data =
static_cast<double*>(allocator.allocate(SIZE * sizeof(double)));
std::cout << "Allocated " << (SIZE * sizeof(double)) << " bytes using the "
<< allocator.getName() << " allocator." << std::endl;
std::cout << "Reallocating data (" << data << ") to size "
<< REALLOCATED_SIZE << "...";
// _sphinx_tag_tut_realloc_start
data = static_cast<double*>(rm.reallocate(data, REALLOCATED_SIZE));
// _sphinx_tag_tut_realloc_end
std::cout << "done. Reallocated data (" << data << ")" << std::endl;
allocator.deallocate(data);
}
return 0;
}