YAKL
YAKL_Gator.h
Go to the documentation of this file.
1 
6 #pragma once
7 // Included by YAKL.h
8 
9 #include "YAKL_LinearAllocator.h"
10 
12 namespace yakl {
13 
26  class Gator {
27  protected:
29  std::string pool_name;
31  std::list<LinearAllocator> pools; // The pools managed by this class
33  std::function<void *( size_t )> mymalloc; // allocation function
35  std::function<void( void * )> myfree; // free function
37  std::function<void( void *, size_t )> myzero; // zero function
39  size_t growSize; // Amount by which the pool grows in bytes
41  size_t blockSize; // Minimum allocation size
43  std::string error_message_cannot_grow;
45  std::string error_message_out_of_memory;
47  size_t high_water_mark; // memory high water mark
49  size_t bytes_currently_allocated; // memory high water mark
50 
52  std::mutex mtx1; // Internal mutex used to protect alloc and free in threaded regions
53  std::mutex mtx2; // Internal mutex used to protect alloc and free in threaded regions
54 
56  void die(std::string str="") {
57  std::cerr << str << std::endl;
58  throw std::runtime_error(str);
59  }
60 
61 
62  struct WaitEntry {
63  std::string label;
64  void *ptr;
65  std::vector<Event> events;
66  };
67 
68  std::vector<WaitEntry> waiting_entries;
69  std::vector<Event> waiting_events;
70 
71 
72  public:
73 
75  Gator() {
76  }
77 
78 
79  // No moves allowed
81  Gator ( Gator && );
83  Gator &operator= ( Gator && );
85  Gator (const Gator & ) = delete;
87  Gator &operator= (const Gator & ) = delete;
88 
89 
91  ~Gator() {
92  finalize();
93  }
94 
95 
111  void init(std::function<void *( size_t )> mymalloc = [] (size_t bytes) -> void * { return ::malloc(bytes); },
112  std::function<void( void * )> myfree = [] (void *ptr) { ::free(ptr); } ,
113  std::function<void( void *, size_t )> myzero = [] (void *ptr, size_t bytes) {} ,
114  size_t initialSize = 1024*1024*1024 ,
115  size_t growSize = 1024*1024*1024 ,
116  size_t blockSize = 16*sizeof(size_t) ,
117  std::string pool_name = "Gator" ,
118  std::string error_message_out_of_memory = "" ,
119  std::string error_message_cannot_grow = "" ) {
120  this->mymalloc = mymalloc ;
121  this->myfree = myfree ;
122  this->myzero = myzero ;
123  this->growSize = growSize ;
124  this->blockSize = blockSize;
125  this->pool_name = pool_name;
126  this->error_message_out_of_memory = error_message_out_of_memory;
127  this->error_message_cannot_grow = error_message_cannot_grow ;
128  this->high_water_mark = 0;
129  this->bytes_currently_allocated = 0;
130 
131  // Create the initial pool if the pool allocator is to be used
132  pools.push_back( LinearAllocator( initialSize , blockSize , mymalloc , myfree , myzero ,
133  pool_name , error_message_out_of_memory) );
134  }
135 
136 
139  void finalize() {
140  if (pools.size() > 0) {
141  // SYCL rev6: The memory is freed without waiting for operating on
142  // it to be completed. CUDA/HIP performs implicit synchronizations
143  #ifdef YAKL_ARCH_SYCL
144  fence();
145  #endif
146  if (yakl_mainproc()) std::cout << "Pool Memory High Water Mark: " << get_high_water_mark() << std::endl;
147  if (yakl_mainproc()) std::cout << "Pool Memory High Water Efficiency: " << get_pool_high_water_space_efficiency() << std::endl;
149  pools = std::list<LinearAllocator>();
150  high_water_mark = 0;
151  bytes_currently_allocated = 0;
152  }
153  }
154 
155 
160  // Used for debugging mainly. Prints all existing allocations
161  for (auto it = pools.begin() ; it != pools.end() ; it++) {
162  it->printAllocsLeft();
163  }
164  }
165 
166 
175  void * allocate(size_t bytes, char const * label="") {
176  if (bytes == 0) return nullptr;
178  // Loop through the pools and see if there's room. If so, allocate in one of them
179  bool room_found = false; // Whether room exists for the allocation
180  bool linear_bug = false; // Whether there's an apparent bug in the LinearAllocator allocate() function
181  void *ptr; // Allocated pointer
182  // Protect against multiple threads trying to allocate at the same time
183  mtx1.lock();
184  {
185  // Start at the first pool, see if it has room.
186  // If so, allocate in that pool and break. If not, try the next pool.
187  for (auto it = pools.begin() ; it != pools.end() ; it++) {
188  if (it->iGotRoom(bytes)) {
189  ptr = it->allocate(bytes,label);
190  room_found = true;
191  if (ptr == nullptr) linear_bug = true;
192  break;
193  }
194  }
195  // If you've gone through all of the existing pools, and room hasn't been found, then it's time to add a new pool
196  if (!room_found) {
197  if (bytes > growSize) {
198  std::cerr << "ERROR: For the pool allocator labeled \"" << pool_name << "\":" << std::endl;
199  std::cerr << "ERROR: Trying to allocate " << bytes << " bytes (" << bytes/1024./1024./1024. << " GB), "
200  << "but the current pool is too small, and growSize is only "
201  << growSize << " bytes (" << growSize/1024./1024./1024. << " GB). \nThus, the allocation will never fit in pool memory.\n";
202  std::cerr << "This can happen for a number of reasons. \nCheck the size of the variable being allocated in the "
203  << "line above and see if it's what you expected. \nIf it's absurdly large, then you might have tried "
204  << "to pass in a negative value for the size, or the size got corrupted somehow. \nNOTE: If you compiled "
205  << "for the wrong GPU artchitecture, it sometimes shows up here as well. \nIf the size of the variable "
206  << "is realistic, then you should increase the initial pool size and probably the grow size as "
207  << "well. \nWhen individual variables consume sizable percentages of a pool, memory gets fragmented, and "
208  << "the pool space isn't used efficiently. \nLarger pools will improve that. "
209  << "\nIn the extreme, you could create "
210  << "an initial pool that consumes most of the avialable device memory. \nIf that still doesn't work, then "
211  << "it sounds like you're choosing a problem size that's too large for the number of compute "
212  << "nodes you're using.\n";
213  std::cerr << error_message_cannot_grow << std::endl;
214  printAllocsLeft();
215  die();
216  }
217  pools.push_back( LinearAllocator( growSize , blockSize , mymalloc , myfree , myzero ,
218  pool_name , error_message_out_of_memory) );
219  ptr = pools.back().allocate(bytes,label);
220  }
221  bytes_currently_allocated += ( (bytes-1)/blockSize+1 )*blockSize;
222  high_water_mark = std::max( high_water_mark , bytes_currently_allocated );
223  }
224  mtx1.unlock();
225  if (linear_bug) {
226  std::cerr << "ERROR: For the pool allocator labeled \"" << pool_name << "\":" << std::endl;
227  die("ERROR: It looks like you've found a bug in LinearAllocator. Please report this at github.com/mrnorman/YAKL");
228  }
229  if (ptr != nullptr) {
230  return ptr;
231  } else {
232  std::cerr << "ERROR: For the pool allocator labeled \"" << pool_name << "\":" << std::endl;
233  std::cerr << "Unable to allocate pointer. It looks like you might have run out of memory.";
234  die( error_message_out_of_memory );
235  }
236  return nullptr;
237  };
238 
239 
242  void free(void *ptr , char const * label = "" ) {
243  bool pointer_valid = false;
244  // Protect against multiple threads trying to free at the same time
245  mtx1.lock();
246  {
247  // Go through each pool. If the pointer lives in that pool, then free it.
248  for (auto it = pools.rbegin() ; it != pools.rend() ; it++) {
249  if (it->thisIsMyPointer(ptr)) {
250  size_t bytes = it->free(ptr,label);
251  bytes_currently_allocated -= bytes;
252  pointer_valid = true;
253  break;
254  }
255  }
256  }
257  mtx1.unlock();
258  if (!pointer_valid) {
259  std::cerr << "ERROR: For the pool allocator labeled \"" << pool_name << "\":" << std::endl;
260  std::cerr << "ERROR: Trying to free an invalid pointer\n";
261  die("This means you have either already freed the pointer, or its address has been corrupted somehow.");
262  }
263  };
264 
265 
268  void free_with_event_dependencies(void *ptr , std::vector<Event> events_in , char const * label = "") {
269  mtx2.lock();
270  waiting_entries.push_back( { std::string(label) , ptr , events_in } );
271  for (int ind_event_in=0; ind_event_in < events_in.size(); ind_event_in++) {
272  bool add_new_event = true;
273  for (int ind_event_list=0; ind_event_list < waiting_events.size(); ind_event_list++) {
274  if (events_in[ind_event_in] == waiting_events[ind_event_list]) add_new_event = false;
275  }
276  if (add_new_event) waiting_events.push_back( events_in[ind_event_in] );
277  }
278  mtx2.unlock();
279  };
280 
281 
285  mtx2.lock();
286  // Loop through waiting events. Check if it's completed
287  for (int ievent=0; ievent < waiting_events.size(); ievent++) {
288  auto waiting_event = waiting_events[ievent];
289  // If this event has completed, erase all instances of this event from all waiting entry event lists
290  if (waiting_event.completed()) {
291  auto &completed_event = waiting_event;
292  // Loop through waiting entries and erase completed event from each entry's waiting event list
293  for (int ientry=0; ientry < waiting_entries.size(); ientry++) {
294  auto waiting_entry = waiting_entries[ientry];
295  // Search through this waiting entry's waiting event list, and erase any occurrences of this completed event
296  for (int i=0; i < waiting_entry.events.size(); i++) {
297  if (waiting_entry.events[i] == completed_event) waiting_entry.events.erase(waiting_entry.events.begin()+i);
298  break;
299  }
300  // if this waiting entry's waiting event list is empty, free its pointer & erase it from waiting entry list
301  if (waiting_entry.events.empty()) {
302  this->free( waiting_entry.ptr , waiting_entry.label.c_str() );
303  waiting_entries.erase( waiting_entries.begin()+ientry );
304  }
305  }
306  // Finally, erase this completed event from the waiting events list
307  waiting_events.erase(waiting_events.begin()+ievent);
308  }
309  }
310  mtx2.unlock();
311  }
312 
313 
315  size_t get_pool_capacity( ) const {
316  size_t sz = 0;
317  for (auto it = pools.begin() ; it != pools.end() ; it++) { sz += it->poolSize(); }
318  return sz;
319  }
320 
321 
323  size_t get_num_allocs( ) const {
324  size_t allocs = 0;
325  for (auto it = pools.begin() ; it != pools.end() ; it++) { allocs += it->numAllocs(); }
326  return allocs;
327  }
328 
329 
331  size_t get_high_water_mark() const { return high_water_mark; }
332 
333 
335  int get_num_pools() const { return pools.size(); }
336 
337 
340  return bytes_currently_allocated;
341  }
342 
343 
345  double get_pool_space_efficiency() const {
346  return static_cast<double>(get_bytes_currently_allocated()) / static_cast<double>(get_pool_capacity());
347  }
348 
349 
352  return static_cast<double>(get_high_water_mark()) / static_cast<double>(get_pool_capacity());
353  }
354 
355 
356  };
357 
358 }
yakl::Gator::free_completed_waiting_entries
void free_completed_waiting_entries()
Check all deallcation entries that are waiting on stream events to see if those events have completed...
Definition: YAKL_Gator.h:284
yakl::Gator::Gator
Gator()
Please use the init() function to specify parameters, not the constructor.
Definition: YAKL_Gator.h:75
yakl::Gator::get_num_allocs
size_t get_num_allocs() const
Get the total number of allocations in all of the pools put together.
Definition: YAKL_Gator.h:323
yakl::Gator::get_pool_high_water_space_efficiency
double get_pool_high_water_space_efficiency() const
Get the proportion of total capacity among pools that has been allocated at this largest past memory ...
Definition: YAKL_Gator.h:351
yakl::Gator::~Gator
~Gator()
All pools are automatically finalized when a Gator object is destroyed.
Definition: YAKL_Gator.h:91
yakl::Gator::WaitEntry
Definition: YAKL_Gator.h:62
yakl::Gator::operator=
Gator & operator=(Gator &&)
A Gator object may be moved but not copied.
yakl::Gator::mtx2
std::mutex mtx2
Definition: YAKL_Gator.h:53
yakl::Gator::WaitEntry::events
std::vector< Event > events
Definition: YAKL_Gator.h:65
__YAKL_NAMESPACE_WRAPPER_END__
#define __YAKL_NAMESPACE_WRAPPER_END__
Definition: YAKL.h:20
yakl::Gator::allocate
void * allocate(size_t bytes, char const *label="")
Allocate the requested number of bytes using the requested label, and return the pointer to allocated...
Definition: YAKL_Gator.h:175
__YAKL_NAMESPACE_WRAPPER_BEGIN__
#define __YAKL_NAMESPACE_WRAPPER_BEGIN__
Definition: YAKL.h:19
yakl::Gator::get_bytes_currently_allocated
size_t get_bytes_currently_allocated() const
Get the current number of bytes that have been allocated in the pools (this is actual allocation,...
Definition: YAKL_Gator.h:339
yakl::Gator::get_num_pools
int get_num_pools() const
Get the current number of pools that have been allocated.
Definition: YAKL_Gator.h:335
yakl::Gator
YAKL Pool allocator class.
Definition: YAKL_Gator.h:26
yakl::fence
void fence()
Block the host code until all device code has completed.
Definition: YAKL_fence.h:16
yakl::Gator::free_with_event_dependencies
void free_with_event_dependencies(void *ptr, std::vector< Event > events_in, char const *label="")
Free the passed pointer, and return the pointer to allocated space.
Definition: YAKL_Gator.h:268
yakl::Gator::get_high_water_mark
size_t get_high_water_mark() const
Get the current memory high water mark in bytes for all allocations passing through the pool.
Definition: YAKL_Gator.h:331
yakl::Gator::WaitEntry::label
std::string label
Definition: YAKL_Gator.h:63
yakl::Gator::finalize
void finalize()
Finalize the pool allocator, deallocate all individual pools.
Definition: YAKL_Gator.h:139
yakl::Gator::WaitEntry::ptr
void * ptr
Definition: YAKL_Gator.h:64
yakl::yakl_mainproc
bool yakl_mainproc()
If true, this is the main MPI process (task number == 0)
Definition: YAKL_error.h:64
yakl::Gator::waiting_events
std::vector< Event > waiting_events
Definition: YAKL_Gator.h:69
yakl::Gator::free
void free(void *ptr, char const *label="")
Free the passed pointer, and return the pointer to allocated space.
Definition: YAKL_Gator.h:242
yakl
yakl::Gator::get_pool_space_efficiency
double get_pool_space_efficiency() const
Get the current proportion of total capacity among pools that is actually allocated.
Definition: YAKL_Gator.h:345
yakl::Gator::waiting_entries
std::vector< WaitEntry > waiting_entries
Definition: YAKL_Gator.h:68
yakl::Gator::get_pool_capacity
size_t get_pool_capacity() const
Get the total capacity of all of the pools put together.
Definition: YAKL_Gator.h:315
yakl::Gator::init
void init(std::function< void *(size_t)> mymalloc=[](size_t bytes) -> void *{ return ::malloc(bytes);}, std::function< void(void *)> myfree=[](void *ptr) { ::free(ptr);}, std::function< void(void *, size_t)> myzero=[](void *ptr, size_t bytes) {}, size_t initialSize=1024 *1024 *1024, size_t growSize=1024 *1024 *1024, size_t blockSize=16 *sizeof(size_t), std::string pool_name="Gator", std::string error_message_out_of_memory="", std::string error_message_cannot_grow="")
Initialize the pool.
Definition: YAKL_Gator.h:111
yakl::Gator::printAllocsLeft
void printAllocsLeft()
[USEFUL FOR DEBUGGING] Print all allocations left in this pool object.
Definition: YAKL_Gator.h:159