Galois
|
Memory allocators are used by dynamic data structures whose size is either known at runtime or changes during execution, e.g. std::vector, std::set, to manage dynamically changing memory requirements. The default allocation operations ("malloc", "new", "delete", etc.) provided by the C/C++ language and standard library are designed to be general-purpose. This generality comes at the cost of performance. For instance, your code could be single-threaded, but since malloc is designed to handle multi-threaded paradigms just as well, this extra functionality degrades the performance.
For thread-safe memory allocation, these functions often use a single lock on their internal heap, and, therefore, do not scale with multiple threads. As a result, parallel code that uses the default C/C++ allocators does not scale beyond a few threads. A good memory allocator is:
Keeping these design goals in mind, in Galois, we have designed scalable parallel allocators for some of the common usage patterns. Some of them are described below.
A common usage pattern is to allocate objects of the same type inside a parallel loop. Such an allocator can be implemented easily in a scalable manner. Galois provides galois::FixedSizeAllocator, which takes a template parameter as the type of object to be allocated. It only supports objects of fixed size type and always allocates the chunk of memory required to store one element of that size. Therefore, it cannot be used with STL data structures like std::vector and std::deque, which may need to allocate variable sized chunks of memory in order to keep all the elements contiguous in memory.
galois::FixedSizeAllocator can be used to allocate elements of STL data structures like std::set, and std::list, etc., which always allocate objects of fixed size type and elements are not required to be contiguous in memory. The source for galois::ThreadSafeOrderedSet shows how a fixed size allocator can be passed to std::set. Below is an example of using galois::FixedSizeAllocator:
Per-iteration allocator galois::PerIterAllocTy can be used when dynamic data structures are defined inside the parallel loop and live for only one iteration of the loop. This allocator can be used inside the operators for galois::for_each. It can be used with STL data structures, e.g., std::vector and std::set etc., and supports variable size allocations.
To use per-iteration allocator, you need to pass galois::per_iter_alloc to galois::for_each. Here is an example of how to use it:
Power-of-2 allocator galois::Pow_2_VarSizeAlloc is a scalable allocator for dynamic data structures that allocate objects with variable size. This is a suitable allocator for STL data structures such as std::vector, std::deque, etc. It allocates blocks of sizes in powers of 2 so that insertion operations on containers like std::vector get amortized over time.
The following snippet shows how to define and use a std::vector of integers using power-of-2 allocator:
The allocators in Galois are implemented in a modular fashion. An allocator can take a heap implementation as its template argument. A heap implementation defines two functions:
void* allocate(size_t)
, andvoid deallocate(void*, size_t)
.Several heap implementations are just wrappers around another heap implementation to modify its interface in some way. In the following we list a few of them and show an example:
Per-iteration allocator, from include/galois/Mem.h, shows how heap implementations can be combined together to form useful allocators:
Another example, from include/galois/runtime/Mem.h, shows how the Fixed Size allocator is defined:
galois::runtime::ExternalHeapAllocator is a wrapper class that can be used to wrap any third-party heap implementation, and make it use-able by Galois and STL data structures. It takes the type-name of the heap implementation as a template parameter and a reference to it for instantiating the allocator object.
As an example, we can have a C++ memory allocator by wrapping malloc and free, from C, as follows (see file include/galois/runtime/Mem.h):
Now we can wrap MallocHeap with galois::runtime::ExternalHeapAllocator, and use the wrapped allocator with STL containers. Below shows an example from lonestar/tutorial_examples/ThirdPartyMalloc.cpp: