CPPuddle
Loading...
Searching...
No Matches
buffer_management.hpp
Go to the documentation of this file.
1// LANL Copyright Notice:
2//
3//© 2026. Triad National Security, LLC. All rights reserved.
4//
5// This program was produced under U.S. Government contract 89233218CNA000001
6// for Los Alamos National Laboratory (LANL), which is operated by Triad
7// National Security, LLC for the U.S. Department of Energy/National Nuclear
8// Security Administration. All rights in the program are reserved by Triad
9// National Security, LLC, and the U.S. Department of Energy/National Nuclear
10// Security Administration. The Government is granted for itself and others
11// acting on its behalf a nonexclusive, paid-up, irrevocable worldwide license
12// in this material to reproduce, prepare. derivative works, distribute copies
13// to the public, perform publicly and display publicly, and to permit others
14// to do so.
15//
16//
17// Distributed under the Boost Software License, Version 1.0. (See accompanying
18// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
19
20#ifndef BUFFER_MANAGEMENT_HPP
21#define BUFFER_MANAGEMENT_HPP
22
23#include <atomic>
24#include <cassert>
25#include <functional>
26#include <iostream>
27#include <list>
28#include <memory>
29#include <mutex>
30#include <optional>
31#include <stdexcept>
32#include <type_traits>
33#include <unordered_map>
34
35// Warn about suboptimal performance without correct HPX-aware allocators
36#ifdef CPPUDDLE_HAVE_HPX
37#ifndef CPPUDDLE_HAVE_HPX_AWARE_ALLOCATORS
38#pragma message \
39"Warning: CPPuddle build with HPX support but without HPX-aware allocators enabled. \
40For better performance configure CPPuddle with CPPUDDLE_WITH_HPX_AWARE_ALLOCATORS=ON!"
41#else
42// include runtime to get HPX thread IDs required for the HPX-aware allocators
43#include <hpx/include/runtime.hpp>
44#endif
45#endif
46
47#if defined(CPPUDDLE_HAVE_HPX) && defined(CPPUDDLE_HAVE_HPX_MUTEX)
48// For builds with The HPX mutex
49#include <hpx/mutex.hpp>
50#endif
51
52#ifdef CPPUDDLE_HAVE_COUNTERS
53#include <boost/core/demangle.hpp>
54#if defined(CPPUDDLE_HAVE_HPX)
55#include <hpx/include/performance_counters.hpp>
56#endif
57#endif
58
60
61namespace cppuddle {
62namespace memory_recycling {
63
64namespace device_selection {
66
69template <typename T, typename Allocator> struct select_device_functor {
70 void operator()(const size_t device_id) {
71 if constexpr (max_number_gpus > 1)
72 throw std::runtime_error(
73 "Allocators used in Multi-GPU builds need explicit Multi-GPU support "
74 "(by having a select_device_functor overload");
75 }
76};
77} // namespace device_selection
78
79namespace detail {
80
82class buffer_interface {
83public:
84#if defined(CPPUDDLE_DEACTIVATE_BUFFER_RECYCLING)
85
86// Warn about suboptimal performance without recycling
87#pragma message \
88"Warning: Building without buffer recycling! Use only for performance testing! \
89For better performance configure CPPuddle with CPPUDDLE_WITH_BUFFER_RECYCLING=ON!"
90
91 template <typename T, typename Host_Allocator>
92 static T *get(size_t number_elements, bool manage_content_lifetime = false,
93 std::optional<size_t> location_hint = std::nullopt,
94 std::optional<size_t> device_id = std::nullopt) {
95
96 return Host_Allocator{}.allocate(number_elements);
97 }
99 template <typename T, typename Host_Allocator>
100 static void mark_unused(T *p, size_t number_elements,
101 std::optional<size_t> location_hint = std::nullopt,
102 std::optional<size_t> device_id = std::nullopt) {
103 return Host_Allocator{}.deallocate(p, number_elements);
104 }
105#else
110 template <typename T, typename Host_Allocator>
111 static T *get(size_t number_elements, bool manage_content_lifetime = false,
112 std::optional<size_t> location_hint = std::nullopt,
113 std::optional<size_t> device_id = std::nullopt) {
114 try {
115 return buffer_manager<T, Host_Allocator>::get(
116 number_elements, manage_content_lifetime, location_hint, device_id);
117 } catch (const std::exception &exc) {
118 std::cerr << "ERROR: Encountered unhandled exception in cppuddle get: " << exc.what() << std::endl;
119 std::cerr << "Rethrowing exception... " << std::endl;;
120 throw;
121 }
122 }
123
127 template <typename T, typename Host_Allocator>
128 static void mark_unused(T *p, size_t number_elements,
129 std::optional<size_t> location_hint = std::nullopt,
130 std::optional<size_t> device_id = std::nullopt) {
131 try {
132 return buffer_manager<T, Host_Allocator>::mark_unused(p, number_elements,
133 location_hint, device_id);
134 } catch (const std::exception &exc) {
135 std::cerr << "ERROR: Encountered unhandled exception in cppuddle mark_unused: " << exc.what() << std::endl;
136 std::cerr << "Rethrowing exception... " << std::endl;;
137 throw;
138 }
139 }
140#endif
142 template <typename T, typename Host_Allocator>
144#ifdef CPPUDDLE_HAVE_COUNTERS
145 buffer_manager<T, Host_Allocator>::register_counters_with_hpx();
146#else
147 std::cerr << "Warning: Trying to register allocator performance counters "
148 "with HPX but CPPuddle was built "
149 "without CPPUDDLE_WITH_COUNTERS -- operation will be ignored!"
150 << std::endl;
151#endif
152 }
153
155 static void clean_all() {
156 std::lock_guard<mutex_t> guard(instance().callback_protection_mut);
157 for (const auto &clean_function :
158 instance().total_cleanup_callbacks) {
159 clean_function();
160 }
161 }
162
163 static void clean_unused_buffers() {
164 std::lock_guard<mutex_t> guard(instance().callback_protection_mut);
165 for (const auto &clean_function :
166 instance().partial_cleanup_callbacks) {
167 clean_function();
168 }
169 }
170
171 static void finalize() {
172 std::lock_guard<mutex_t> guard(instance().callback_protection_mut);
173 for (const auto &finalize_function :
174 instance().finalize_callbacks) {
175 finalize_function();
176 }
177 }
178
180#ifdef CPPUDDLE_HAVE_COUNTERS
181 std::lock_guard<mutex_t> guard(instance().callback_protection_mut);
182 for (const auto &print_function :
183 instance().print_callbacks) {
184 print_function();
185 }
186#else
187 std::cerr << "Warning: Trying to print allocator performance counters but CPPuddle was built "
188 "without CPPUDDLE_WITH_COUNTERS -- operation will be ignored!"
189 << std::endl;
190#endif
191 }
192
193 // Member variables and methods
194private:
195
197 static buffer_interface& instance() {
198 static buffer_interface singleton{};
199 return singleton;
200 }
202 std::list<std::function<void()>> print_callbacks;
205 std::list<std::function<void()>> finalize_callbacks;
208 std::list<std::function<void()>> total_cleanup_callbacks;
211 std::list<std::function<void()>> partial_cleanup_callbacks;
214 buffer_interface() = default;
215
216 mutex_t callback_protection_mut;
218 static void add_total_cleanup_callback(const std::function<void()> &func) {
219 std::lock_guard<mutex_t> guard(instance().callback_protection_mut);
220 instance().total_cleanup_callbacks.push_back(func);
221 }
224 static void add_partial_cleanup_callback(const std::function<void()> &func) {
225 std::lock_guard<mutex_t> guard(instance().callback_protection_mut);
226 instance().partial_cleanup_callbacks.push_back(func);
227 }
230 static void add_finalize_callback(const std::function<void()> &func) {
231 std::lock_guard<mutex_t> guard(instance().callback_protection_mut);
232 instance().finalize_callbacks.push_back(func);
233 }
236 static void add_print_callback(const std::function<void()> &func) {
237 std::lock_guard<mutex_t> guard(instance().callback_protection_mut);
238 instance().print_callbacks.push_back(func);
239 }
240
241public:
242 ~buffer_interface() = default;
243
244 // Subclasses
245private:
247 template <typename T, typename Host_Allocator> class buffer_manager {
248 private:
249 // Tuple content: Pointer to buffer, buffer_size, location ID, Flag
250 // The flag at the end controls whether to buffer content is to be reused as
251 // well
252 using buffer_entry_type = std::tuple<T *, size_t, size_t, bool>;
253
254
255 public:
257 static void clean() {
258 assert(instance() && !is_finalized);
259 for (auto i = 0; i < number_instances * max_number_gpus; i++) {
260 std::lock_guard<mutex_t> guard(instance()[i].mut);
261 instance()[i].clean_all_buffers();
262 }
263 }
264 static void print_performance_counters() {
265 assert(instance() && !is_finalized);
266 for (auto i = 0; i < number_instances * max_number_gpus; i++) {
267 std::lock_guard<mutex_t> guard(instance()[i].mut);
268 instance()[i].print_counters();
269 }
270 }
271 static void finalize() {
272 assert(instance() && !is_finalized);
273 is_finalized = true;
274 for (auto i = 0; i < number_instances * max_number_gpus; i++) {
275 std::lock_guard<mutex_t> guard(instance()[i].mut);
276 instance()[i].clean_all_buffers();
277 }
278 instance().reset();
279 }
281 static void clean_unused_buffers_only() {
282 assert(instance() && !is_finalized);
283 for (auto i = 0; i < number_instances * max_number_gpus; i++) {
284 std::lock_guard<mutex_t> guard(instance()[i].mut);
285 for (auto &unused_buffer_list_tuple : instance()[i].unused_buffer_map) {
286 auto &unused_buffer_list = std::get<1>(unused_buffer_list_tuple);
287 for (auto &buffer_tuple : unused_buffer_list) {
288 Host_Allocator alloc;
289 if (std::get<3>(buffer_tuple)) {
290 std::destroy_n(std::get<0>(buffer_tuple), std::get<1>(buffer_tuple));
291 }
292 alloc.deallocate(std::get<0>(buffer_tuple), std::get<1>(buffer_tuple));
293 }
294 unused_buffer_list.clear();
295 }
296 }
297 }
298#if defined(CPPUDDLE_HAVE_COUNTERS) && defined(CPPUDDLE_HAVE_HPX)
299 static size_t get_sum_number_recycling(bool reset) {
300 if (reset)
301 sum_number_recycling = 0;
302 return sum_number_recycling;
303 }
304 static size_t get_sum_number_allocation(bool reset) {
305 if (reset)
306 sum_number_allocation = 0;
307 return sum_number_allocation;
308 }
309 static size_t get_sum_number_creation(bool reset) {
310 if (reset)
311 sum_number_creation = 0;
312 return sum_number_creation;
313 }
314 static size_t get_sum_number_deallocation(bool reset) {
315 if (reset)
316 sum_number_deallocation = 0;
317 return sum_number_deallocation;
318 }
319 static size_t get_sum_number_wrong_hints(bool reset) {
320 if (reset)
321 sum_number_wrong_hints = 0;
322 return sum_number_wrong_hints;
323 }
324 static size_t get_sum_number_wrong_device_hints(bool reset) {
325 if (reset)
326 sum_number_wrong_hints = 0;
327 return sum_number_wrong_device_hints;
328 }
329 static size_t get_sum_number_bad_allocs(bool reset) {
330 if (reset)
331 sum_number_bad_allocs = 0;
332 return sum_number_bad_allocs;
333 }
334
335 static void register_counters_with_hpx(void) {
336 std::string alloc_name =
337 boost::core::demangle(typeid(Host_Allocator).name()) +
338 std::string("_") + boost::core::demangle(typeid(T).name());
339 hpx::performance_counters::install_counter_type(
340 std::string("/cppuddle/allocators/") + alloc_name + std::string("/number_recycling/"),
341 &get_sum_number_recycling,
342 "Number of allocations using a recycled buffer with this "
343 "allocator");
344 hpx::performance_counters::install_counter_type(
345 std::string("/cppuddle/allocators/") + alloc_name + std::string("/number_allocations/"),
346 &get_sum_number_allocation,
347 "Number of allocations with this allocator");
348 hpx::performance_counters::install_counter_type(
349 std::string("/cppuddle/allocators/") + alloc_name + std::string("/number_creations/"),
350 &get_sum_number_creation,
351 "Number of allocations not using a recycled buffer with this "
352 "allocator");
353 hpx::performance_counters::install_counter_type(
354 std::string("/cppuddle/allocators/") + alloc_name + std::string("/number_deallocations/"),
355 &get_sum_number_deallocation,
356 "Number of deallocations yielding buffers to be recycled with this "
357 "allocator");
358 hpx::performance_counters::install_counter_type(
359 std::string("/cppuddle/allocators/") + alloc_name + std::string("/number_wrong_hints/"),
360 &get_sum_number_wrong_hints,
361 "Number of wrong hints supplied to the dealloc method with this allocator");
362 hpx::performance_counters::install_counter_type(
363 std::string("/cppuddle/allocators/") + alloc_name + std::string("/number_wrong_device_hints/"),
364 &get_sum_number_wrong_device_hints,
365 "Number of wrong device hints supplied to the dealloc method with this allocator");
366 hpx::performance_counters::install_counter_type(
367 std::string("/cppuddle/allocators/") + alloc_name + std::string("/number_bad_allocs/"),
368 &get_sum_number_bad_allocs,
369 "Number of wrong bad allocs which triggered a cleanup of unused buffers");
370 }
371#endif
372
374 static T *get(size_t number_of_elements, bool manage_content_lifetime,
375 std::optional<size_t> location_hint = std::nullopt,
376 std::optional<size_t> gpu_device_id = std::nullopt) {
377 init_callbacks_once();
378 if (is_finalized) {
379 throw std::runtime_error("Tried allocation after finalization");
380 }
381 assert(instance() && !is_finalized);
382
383 size_t location_id = 0;
384 if (location_hint) {
385 location_id = *location_hint;
386 }
387 if (location_id >= number_instances) {
388 throw std::runtime_error("Tried to create buffer with invalid location_id [get]");
389 }
390 size_t device_id = 0;
391 if (gpu_device_id) {
392 device_id = *gpu_device_id;
393 }
394 if (device_id >= max_number_gpus) {
395 throw std::runtime_error("Tried to create buffer with invalid device id [get]! "
396 "Is multigpu support enabled with the correct number "
397 "of GPUs?");
398 }
399
400 location_id = location_id + device_id * number_instances;
401 std::lock_guard<mutex_t> guard(instance()[location_id].mut);
402
403 std::list<buffer_entry_type> &unused_buffer_list = instance()[location_id].unused_buffer_map[number_of_elements];
404
405
406#ifdef CPPUDDLE_HAVE_COUNTERS
407 instance()[location_id].number_allocation++;
408 sum_number_allocation++;
409#endif
410 // Check for unused buffers we can recycle:
411 for (auto iter = unused_buffer_list.begin();
412 iter != unused_buffer_list.end(); iter++) {
413 auto tuple = *iter;
414 if (std::get<1>(tuple) == number_of_elements) {
415 unused_buffer_list.erase(iter);
416
417 // handle the switch from aggressive to non aggressive reusage (or
418 // vice-versa)
419 if (manage_content_lifetime && !std::get<3>(tuple)) {
420 std::uninitialized_value_construct_n(std::get<0>(tuple),
421 number_of_elements);
422 std::get<3>(tuple) = true;
423 } else if (!manage_content_lifetime && std::get<3>(tuple)) {
424 std::destroy_n(std::get<0>(tuple), std::get<1>(tuple));
425 std::get<3>(tuple) = false;
426 }
427 instance()[location_id].buffer_map.insert({std::get<0>(tuple), tuple});
428#ifdef CPPUDDLE_HAVE_COUNTERS
429 instance()[location_id].number_recycling++;
430 sum_number_recycling++;
431#endif
432 return std::get<0>(tuple);
433 }
434 }
435
436 // No unused buffer found -> Create new one and return it
437 try {
439 T, Host_Allocator>{}(device_id);
440 Host_Allocator alloc;
441 T *buffer = alloc.allocate(number_of_elements);
442 instance()[location_id].buffer_map.insert(
443 {buffer, std::make_tuple(buffer, number_of_elements, 1,
444 manage_content_lifetime)});
445#ifdef CPPUDDLE_HAVE_COUNTERS
446 instance()[location_id].number_creation++;
447 sum_number_creation++;
448#endif
449 if (manage_content_lifetime) {
450 std::uninitialized_value_construct_n(buffer, number_of_elements);
451 }
452 return buffer;
453 } catch (std::bad_alloc &e) {
454 // not enough memory left! Cleanup and attempt again:
455 std::cerr
456 << "Not enough memory left. Cleaning up unused buffers now..."
457 << std::endl;
459 std::cerr << "Buffers cleaned! Try allocation again..." << std::endl;
460
461 // If there still isn't enough memory left, the caller has to handle it
462 // We've done all we can in here
463 Host_Allocator alloc;
465 T, Host_Allocator>{}(device_id);
466 T *buffer = alloc.allocate(number_of_elements);
467 instance()[location_id].buffer_map.insert(
468 {buffer, std::make_tuple(buffer, number_of_elements, 1,
469 manage_content_lifetime)});
470#ifdef CPPUDDLE_HAVE_COUNTERS
471 instance()[location_id].number_creation++;
472 sum_number_creation++;
473 instance()[location_id].number_bad_alloc++;
474 sum_number_bad_allocs++;
475#endif
476 std::cerr << "Second attempt allocation successful!" << std::endl;
477 if (manage_content_lifetime) {
478 std::uninitialized_value_construct_n(buffer, number_of_elements);
479 }
480 return buffer;
481 }
482 }
483
484 static void mark_unused(T *memory_location, size_t number_of_elements,
485 std::optional<size_t> location_hint = std::nullopt,
486 std::optional<size_t> device_hint = std::nullopt) {
487 if (is_finalized)
488 return;
489 assert(instance() && !is_finalized);
490
491 size_t location_id = 0;
492 if (location_hint) {
493 location_id = *location_hint;
494 if (location_id >= number_instances) {
495 throw std::runtime_error(
496 "Buffer recylcer received invalid location hint [mark_unused]");
497 }
498 }
499 size_t device_id = 0;
500 if (device_hint) {
501 device_id = *device_hint;
502 if (device_id >= max_number_gpus) {
503 throw std::runtime_error(
504 "Buffer recylcer received invalid devce hint [mark_unused]");
505 }
506 }
507
508 // Attempt 1 to find the correct bucket/location: Look at provided hint:
509 if (location_hint) {
510 size_t location_id = location_hint.value() + device_id * number_instances;
511 std::lock_guard<mutex_t> guard(instance()[location_id].mut);
512 if (instance()[location_id].buffer_map.find(memory_location) !=
513 instance()[location_id].buffer_map.end()) {
514#ifdef CPPUDDLE_HAVE_COUNTERS
515 instance()[location_id].number_deallocation++;
516 sum_number_deallocation++;
517#endif
518 auto it = instance()[location_id].buffer_map.find(memory_location);
519 assert(it != instance()[location_id].buffer_map.end());
520 auto &tuple = it->second;
521 // sanity checks:
522 assert(std::get<1>(tuple) == number_of_elements);
523 // move to the unused_buffer list
524 instance()[location_id].unused_buffer_map.at(std::get<1>(tuple)).push_front(tuple);
525 instance()[location_id].buffer_map.erase(memory_location);
526 return; // Success
527 }
528 // hint was wrong
529#ifdef CPPUDDLE_HAVE_COUNTERS
530 instance()[location_id].number_wrong_hints++;
531 sum_number_wrong_hints++;
532#endif
533 }
534 // Failed to find buffer in the specified localtion/device!
535 // Attempt 2 - Look for buffer other locations on the same device...
536 for (size_t location_id = device_id * number_instances;
537 location_id < (device_id + 1) * number_instances; location_id++) {
538 if (location_hint) {
539 if (*location_hint + device_id * max_number_gpus == location_id) {
540 continue; // already tried this -> skip
541 }
542 }
543 std::lock_guard<mutex_t> guard(instance()[location_id].mut);
544 if (instance()[location_id].buffer_map.find(memory_location) !=
545 instance()[location_id].buffer_map.end()) {
546#ifdef CPPUDDLE_HAVE_COUNTERS
547 instance()[location_id].number_deallocation++;
548 sum_number_deallocation++;
549#endif
550 auto it = instance()[location_id].buffer_map.find(memory_location);
551 assert(it != instance()[location_id].buffer_map.end());
552 auto &tuple = it->second;
553 // sanity checks:
554 assert(std::get<1>(tuple) == number_of_elements);
555 // move to the unused_buffer list
556 instance()[location_id].unused_buffer_map.at(std::get<1>(tuple)).push_front(tuple);
557 instance()[location_id].buffer_map.erase(memory_location);
558 return; // Success
559 }
560 }
561 // device hint was wrong
562#ifdef CPPUDDLE_HAVE_COUNTERS
563 if (device_hint) {
564 sum_number_wrong_device_hints++;
565 }
566#endif
567 // Failed to find buffer on the specified device!
568 // Attempt 3 - Look for buffer on other devices...
569 for (size_t local_device_id = 0; local_device_id < max_number_gpus;
570 local_device_id++) {
571 if (local_device_id == device_id)
572 continue; // aldready tried this device
573
574 // Try hint localtion first yet again (though on different device)
575 if (location_hint) {
576 size_t location_id = location_hint.value() + local_device_id * number_instances;
577 std::lock_guard<mutex_t> guard(instance()[location_id].mut);
578 if (instance()[location_id].buffer_map.find(memory_location) !=
579 instance()[location_id].buffer_map.end()) {
580#ifdef CPPUDDLE_HAVE_COUNTERS
581 instance()[location_id].number_deallocation++;
582 sum_number_deallocation++;
583#endif
584 auto it = instance()[location_id].buffer_map.find(memory_location);
585 assert(it != instance()[location_id].buffer_map.end());
586 auto &tuple = it->second;
587 // sanity checks:
588 assert(std::get<1>(tuple) == number_of_elements);
589 // move to the unused_buffer list
590 instance()[location_id].unused_buffer_map.at(std::get<1>(tuple)).push_front(tuple);
591 instance()[location_id].buffer_map.erase(memory_location);
592 return; // Success
593 }
594 }
595 // Failed - check all other localtions on device
596 for (size_t location_id = local_device_id * number_instances;
597 location_id < (local_device_id + 1) * number_instances; location_id++) {
598 if (location_hint) {
599 if (*location_hint + local_device_id * max_number_gpus == location_id) {
600 continue; // already tried this -> skip
601 }
602 }
603 std::lock_guard<mutex_t> guard(instance()[location_id].mut);
604 if (instance()[location_id].buffer_map.find(memory_location) !=
605 instance()[location_id].buffer_map.end()) {
606#ifdef CPPUDDLE_HAVE_COUNTERS
607 instance()[location_id].number_deallocation++;
608 sum_number_deallocation++;
609#endif
610 auto it = instance()[location_id].buffer_map.find(memory_location);
611 assert(it != instance()[location_id].buffer_map.end());
612 auto &tuple = it->second;
613 // sanity checks:
614 assert(std::get<1>(tuple) == number_of_elements);
615 // move to the unused_buffer list
616 instance()[location_id].unused_buffer_map.at(std::get<1>(tuple)).push_front(tuple);
617 instance()[location_id].buffer_map.erase(memory_location);
618 return; // Success
619 }
620 }
621 }
622 // Buffer that is to be deleted is nowhere to be found - we looked everywhere!
623 // =>
624 // Failure! Handle here...
625
626 // TODO Throw exception instead in the futures, as soon as the recycler finalize is
627 // in all user codes
628 /* throw std::runtime_error("Tried to delete non-existing buffer"); */
629
630 // This is odd: Print warning -- however, might also happen with static
631 // buffers using these allocators IF the new finalize was not called. For
632 // now, print warning until all user-code is upgraded to the finalize method.
633 // This allows using current versions of cppuddle with older application code
634 std::cerr
635 << "Warning! Tried to delete non-existing buffer within CPPuddle!"
636 << std::endl;
637 std::cerr << "Did you forget to call recycler::finalize?" << std::endl;
638 }
639
640 private:
642 std::unordered_map<T *, buffer_entry_type> buffer_map{};
644 std::unordered_map<size_t, std::list<buffer_entry_type>> unused_buffer_map{};
646 // std::list<buffer_entry_type> unused_buffer_list{};
648 mutex_t mut;
649#ifdef CPPUDDLE_HAVE_COUNTERS
651 size_t number_allocation{0}, number_deallocation{0}, number_wrong_hints{0},
652 number_recycling{0}, number_creation{0}, number_bad_alloc{0};
653
654 static inline std::atomic<size_t> sum_number_allocation{0},
655 sum_number_deallocation{0}, sum_number_wrong_hints{0},
656 sum_number_wrong_device_hints{0}, sum_number_recycling{0},
657 sum_number_creation{0}, sum_number_bad_allocs{0};
658#endif
661 buffer_manager() = default;
662 buffer_manager&
663 operator=(buffer_manager<T, Host_Allocator> const &other) = default;
664 buffer_manager&
665 operator=(buffer_manager<T, Host_Allocator> &&other) = delete;
666 static std::unique_ptr<buffer_manager[]>& instance(void) {
667 static std::unique_ptr<buffer_manager[]> instances{
668 new buffer_manager[number_instances * max_number_gpus]};
669 return instances;
670 }
671 static void init_callbacks_once(void) {
672 assert(instance());
673#if defined(CPPUDDLE_HAVE_HPX) && defined(CPPUDDLE_HAVE_HPX_MUTEX)
674 static hpx::once_flag flag;
675 hpx::call_once(flag, []() {
676#else
677 static std::once_flag flag;
678 std::call_once(flag, []() {
679#endif
680 is_finalized = false;
681 buffer_interface::add_total_cleanup_callback(clean);
682 buffer_interface::add_partial_cleanup_callback(
683 clean_unused_buffers_only);
684 buffer_interface::add_finalize_callback(
685 finalize);
686#ifdef CPPUDDLE_HAVE_COUNTERS
687 buffer_interface::add_print_callback(
689#endif
690 });
691 }
692 static inline std::atomic<bool> is_finalized;
693
694#ifdef CPPUDDLE_HAVE_COUNTERS
695 void print_counters(void) {
696 if (number_allocation == 0)
697 return;
698 // Print performance counters
699 // size_t number_cleaned = unused_buffer_list.size() + buffer_map.size();
700 //
701 std::cout << "\nBuffer manager destructor for (Alloc: "
702 << boost::core::demangle(typeid(Host_Allocator).name()) << ", Type: "
703 << boost::core::demangle(typeid(T).name())
704 << "):" << std::endl
705 << "--------------------------------------------------------------------"
706 << std::endl
707 << "--> Number of bad_allocs that triggered garbage "
708 "collection: "
709 << number_bad_alloc << std::endl
710 << "--> Number of buffers that got requested from this "
711 "manager: "
712 << number_allocation << std::endl
713 << "--> Number of times an unused buffer got recycled for a "
714 "request: "
715 << number_recycling << std::endl
716 << "--> Number of times a new buffer had to be created for a "
717 "request: "
718 << number_creation << std::endl
719 << "--> Number cleaned up buffers: "
720 " "
721 << number_creation << std::endl // TODO Add actual deallocation counter across map
722 << "--> Number wrong deallocation hints: "
723 " "
724 << number_wrong_hints << std::endl
725 << "--> Number of buffers that were marked as used upon "
726 "cleanup: "
727 << buffer_map.size() << std::endl
728 << "==> Recycle rate: "
729 " "
730 << static_cast<float>(number_recycling) / number_allocation *
731 100.0f
732 << "%" << std::endl;
733 }
734#endif
735
736 void clean_all_buffers(void) {
737#ifdef CPPUDDLE_HAVE_COUNTERS
738 if (number_allocation == 0 && number_recycling == 0 &&
739 number_bad_alloc == 0 && number_creation == 0 &&
740 unused_buffer_map.empty() && buffer_map.empty()) {
741 return;
742 }
743#endif
744 for (auto &unused_buffer_list_tuple : unused_buffer_map) {
745 auto &unused_buffer_list = std::get<1>(unused_buffer_list_tuple);
746 for (auto &buffer_tuple : unused_buffer_list) {
747 Host_Allocator alloc;
748 if (std::get<3>(buffer_tuple)) {
749 std::destroy_n(std::get<0>(buffer_tuple), std::get<1>(buffer_tuple));
750 }
751 alloc.deallocate(std::get<0>(buffer_tuple), std::get<1>(buffer_tuple));
752 }
753 unused_buffer_list.clear();
754 }
755 for (auto &map_tuple : buffer_map) {
756 auto buffer_tuple = map_tuple.second;
757 Host_Allocator alloc;
758 if (std::get<3>(buffer_tuple)) {
759 std::destroy_n(std::get<0>(buffer_tuple), std::get<1>(buffer_tuple));
760 }
761 alloc.deallocate(std::get<0>(buffer_tuple), std::get<1>(buffer_tuple));
762 }
763 buffer_map.clear();
764#ifdef CPPUDDLE_HAVE_COUNTERS
765 number_allocation = 0;
766 number_recycling = 0;
767 number_bad_alloc = 0;
768 number_creation = 0;
769 number_wrong_hints = 0;
770#endif
771 }
772 public:
773 ~buffer_manager() {
774 clean_all_buffers();
775 }
776
777 public: // Putting deleted constructors in public gives more useful error
778 // messages
779 // Bunch of constructors we don't need
780 buffer_manager(
781 buffer_manager<T, Host_Allocator> const &other) = delete;
782 buffer_manager(
783 buffer_manager<T, Host_Allocator> &&other) = delete;
784 };
785
786public:
787 // Putting deleted constructors in public gives more useful error messages
788 // Bunch of constructors we don't need
789 buffer_interface(buffer_interface const &other) = delete;
790 buffer_interface& operator=(buffer_interface const &other) = delete;
791 buffer_interface(buffer_interface &&other) = delete;
792 buffer_interface& operator=(buffer_interface &&other) = delete;
793};
794
795template <typename T, typename Host_Allocator> struct recycle_allocator {
796 using value_type = T;
797 using underlying_allocator_type = Host_Allocator;
798 static_assert(std::is_same_v<value_type, typename underlying_allocator_type::value_type>);
799 const std::optional<size_t> dealloc_hint;
800 const std::optional<size_t> device_id;
801
802#ifndef CPPUDDLE_HAVE_HPX_AWARE_ALLOCATORS
804 : dealloc_hint(std::nullopt), device_id(std::nullopt) {}
805 explicit recycle_allocator(size_t hint) noexcept
806 : dealloc_hint(std::nullopt), device_id(std::nullopt) {}
808 recycle_allocator<T, Host_Allocator> const &other) noexcept
809 : dealloc_hint(std::nullopt), device_id(std::nullopt) {}
810 T *allocate(std::size_t n) {
812 return data;
813 }
814 void deallocate(T *p, std::size_t n) {
816 }
817#else
818 recycle_allocator() noexcept
819 : dealloc_hint(hpx::get_worker_thread_num() % number_instances), device_id(0) {}
820 explicit recycle_allocator(const size_t device_id) noexcept
821 : dealloc_hint(hpx::get_worker_thread_num() % number_instances), device_id(device_id) {}
822 explicit recycle_allocator(const size_t device_i, const size_t location_id) noexcept
823 : dealloc_hint(location_id), device_id(device_id) {}
824 explicit recycle_allocator(
825 recycle_allocator<T, Host_Allocator> const &other) noexcept
826 : dealloc_hint(other.dealloc_hint), device_id(other.device_id) {}
827 T *allocate(std::size_t n) {
828 T *data = buffer_interface::get<T, Host_Allocator>(
829 n, false, hpx::get_worker_thread_num() % number_instances, device_id);
830 return data;
831 }
832 void deallocate(T *p, std::size_t n) {
833 buffer_interface::mark_unused<T, Host_Allocator>(p, n, dealloc_hint,
834 device_id);
835 }
836#endif
837
838 template <typename... Args>
839 inline void construct(T *p, Args... args) noexcept {
840 ::new (static_cast<void *>(p)) T(std::forward<Args>(args)...);
841 }
842 void destroy(T *p) { p->~T(); }
843};
844template <typename T, typename U, typename Host_Allocator>
845constexpr bool
847 recycle_allocator<U, Host_Allocator> const &) noexcept {
848 if constexpr (std::is_same_v<T, U>)
849 return true;
850 else
851 return false;
852}
853template <typename T, typename U, typename Host_Allocator>
854constexpr bool
856 recycle_allocator<U, Host_Allocator> const &) noexcept {
857 if constexpr (std::is_same_v<T, U>)
858 return false;
859 else
860 return true;
861}
862
864template <typename T, typename Host_Allocator>
866 using value_type = T;
867 using underlying_allocator_type = Host_Allocator;
868 static_assert(std::is_same_v<value_type, typename underlying_allocator_type::value_type>);
869 const std::optional<size_t> dealloc_hint;
870 const std::optional<size_t> device_id;
871
872#ifndef CPPUDDLE_HAVE_HPX_AWARE_ALLOCATORS
874 : dealloc_hint(std::nullopt), device_id(std::nullopt) {}
875 explicit aggressive_recycle_allocator(size_t hint) noexcept
876 : dealloc_hint(std::nullopt), device_id(std::nullopt) {}
879 : dealloc_hint(std::nullopt), device_id(std::nullopt) {}
880 T *allocate(std::size_t n) {
882 n, true); // also initializes the buffer if it isn't reused
883 return data;
884 }
885 void deallocate(T *p, std::size_t n) {
887 }
888#else
890 : dealloc_hint(hpx::get_worker_thread_num() % number_instances), device_id(0) {}
891 explicit aggressive_recycle_allocator(const size_t device_id) noexcept
892 : dealloc_hint(hpx::get_worker_thread_num() % number_instances), device_id(device_id) {}
893 explicit aggressive_recycle_allocator(const size_t device_id, const size_t location_id) noexcept
894 : dealloc_hint(location_id), device_id(device_id) {}
895 explicit aggressive_recycle_allocator(
896 recycle_allocator<T, Host_Allocator> const &other) noexcept
897 : dealloc_hint(other.dealloc_hint), device_id(other.device_id) {}
898 T *allocate(std::size_t n) {
899 T *data = buffer_interface::get<T, Host_Allocator>(
900 n, true, dealloc_hint, device_id); // also initializes the buffer
901 // if it isn't reused
902 return data;
903 }
904 void deallocate(T *p, std::size_t n) {
905 buffer_interface::mark_unused<T, Host_Allocator>(p, n, dealloc_hint,
906 device_id);
907 }
908#endif
909
910#ifndef CPPUDDLE_DEACTIVATE_AGGRESSIVE_ALLOCATORS
911 template <typename... Args>
912 inline void construct(T *p, Args... args) noexcept {
913 // Do nothing here - we reuse the content of the last owner
914 }
915 void destroy(T *p) {
916 // Do nothing here - Contents will be destroyed when the buffer manager is
917 // destroyed, not before
918 }
919#else
920// Warn about suboptimal performance without recycling
921#pragma message \
922"Warning: Building without content reusage for aggressive allocators! \
923For better performance configure with CPPUDDLE_WITH_AGGRESSIVE_CONTENT_RECYCLING=ON !"
924 template <typename... Args>
925 inline void construct(T *p, Args... args) noexcept {
926 ::new (static_cast<void *>(p)) T(std::forward<Args>(args)...);
927 }
928 void destroy(T *p) { p->~T(); }
929#endif
930};
931
932template <typename T, typename U, typename Host_Allocator>
933constexpr bool
936 if constexpr (std::is_same_v<T, U>)
937 return true;
938 else
939 return false;
940}
941template <typename T, typename U, typename Host_Allocator>
942constexpr bool
945 if constexpr (std::is_same_v<T, U>)
946 return false;
947 else
948 return true;
949}
950} // namespace detail
951} // namespace memory_recycling
952} // end namespace cppuddle
953
954#endif
Singleton interface to all buffer_managers.
Definition buffer_management.hpp:82
static void print_performance_counters()
Definition buffer_management.hpp:179
static void register_allocator_counters_with_hpx(void)
Register all CPPuddle counters as HPX performance counters.
Definition buffer_management.hpp:143
buffer_interface & operator=(buffer_interface &&other)=delete
buffer_interface & operator=(buffer_interface const &other)=delete
buffer_interface(buffer_interface const &other)=delete
static void clean_all()
Deallocate all buffers, no matter whether they are marked as used or not.
Definition buffer_management.hpp:155
static T * get(size_t number_elements, bool manage_content_lifetime=false, std::optional< size_t > location_hint=std::nullopt, std::optional< size_t > device_id=std::nullopt)
Definition buffer_management.hpp:111
static void finalize()
Deallocate all buffers, no matter whether they are marked as used or not.
Definition buffer_management.hpp:171
buffer_interface(buffer_interface &&other)=delete
static void clean_unused_buffers()
Deallocated all currently unused buffer.
Definition buffer_management.hpp:163
static void mark_unused(T *p, size_t number_elements, std::optional< size_t > location_hint=std::nullopt, std::optional< size_t > device_id=std::nullopt)
Definition buffer_management.hpp:128
Definition buffer_management.hpp:79
constexpr bool operator!=(recycle_allocator< T, Host_Allocator > const &, recycle_allocator< U, Host_Allocator > const &) noexcept
Definition buffer_management.hpp:855
constexpr bool operator==(recycle_allocator< T, Host_Allocator > const &, recycle_allocator< U, Host_Allocator > const &) noexcept
Definition buffer_management.hpp:846
Definition aligned_recycling_allocators.hpp:19
CPPuddle namespace containing the memory recycling functionality.
Definition aligned_recycling_allocators.hpp:17
Definition config.hpp:31
constexpr size_t max_number_gpus
Definition config.hpp:52
constexpr size_t number_instances
Definition config.hpp:50
std::mutex mutex_t
Definition config.hpp:36
Recycles not only allocations but also the contents of a buffer.
Definition buffer_management.hpp:865
aggressive_recycle_allocator(size_t hint) noexcept
Definition buffer_management.hpp:875
void destroy(T *p)
Definition buffer_management.hpp:915
void deallocate(T *p, std::size_t n)
Definition buffer_management.hpp:885
void construct(T *p, Args... args) noexcept
Definition buffer_management.hpp:912
aggressive_recycle_allocator(aggressive_recycle_allocator< T, Host_Allocator > const &) noexcept
Definition buffer_management.hpp:877
T * allocate(std::size_t n)
Definition buffer_management.hpp:880
boost::alignment::aligned_allocator< T, alignement > underlying_allocator_type
Definition buffer_management.hpp:867
aggressive_recycle_allocator() noexcept
Definition buffer_management.hpp:873
Definition buffer_management.hpp:795
T * allocate(std::size_t n)
Definition buffer_management.hpp:810
recycle_allocator(size_t hint) noexcept
Definition buffer_management.hpp:805
void destroy(T *p)
Definition buffer_management.hpp:842
recycle_allocator() noexcept
Definition buffer_management.hpp:803
boost::alignment::aligned_allocator< T, alignement > underlying_allocator_type
Definition buffer_management.hpp:797
void construct(T *p, Args... args) noexcept
Definition buffer_management.hpp:839
void deallocate(T *p, std::size_t n)
Definition buffer_management.hpp:814
recycle_allocator(recycle_allocator< T, Host_Allocator > const &other) noexcept
Definition buffer_management.hpp:807
Default device selector - No MultGPU support.
Definition buffer_management.hpp:69
void operator()(const size_t device_id)
Definition buffer_management.hpp:70