82class buffer_interface {
84#if defined(CPPUDDLE_DEACTIVATE_BUFFER_RECYCLING)
88"Warning: Building without buffer recycling! Use only for performance testing! \
89For better performance configure CPPuddle with CPPUDDLE_WITH_BUFFER_RECYCLING=ON!"
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) {
96 return Host_Allocator{}.allocate(number_elements);
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);
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) {
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;;
127 template <
typename T,
typename Host_Allocator>
129 std::optional<size_t> location_hint = std::nullopt,
130 std::optional<size_t> device_id = std::nullopt) {
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;;
142 template <
typename T,
typename Host_Allocator>
144#ifdef CPPUDDLE_HAVE_COUNTERS
145 buffer_manager<T, Host_Allocator>::register_counters_with_hpx();
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!"
156 std::lock_guard<mutex_t> guard(instance().callback_protection_mut);
157 for (
const auto &clean_function :
158 instance().total_cleanup_callbacks) {
164 std::lock_guard<mutex_t> guard(instance().callback_protection_mut);
165 for (
const auto &clean_function :
166 instance().partial_cleanup_callbacks) {
172 std::lock_guard<mutex_t> guard(instance().callback_protection_mut);
173 for (
const auto &finalize_function :
174 instance().finalize_callbacks) {
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) {
187 std::cerr <<
"Warning: Trying to print allocator performance counters but CPPuddle was built "
188 "without CPPUDDLE_WITH_COUNTERS -- operation will be ignored!"
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;
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);
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);
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);
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);
247 template <
typename T,
typename Host_Allocator>
class buffer_manager {
252 using buffer_entry_type = std::tuple<T *, size_t, size_t, bool>;
257 static void clean() {
258 assert(instance() && !is_finalized);
260 std::lock_guard<mutex_t> guard(instance()[i].mut);
261 instance()[i].clean_all_buffers();
265 assert(instance() && !is_finalized);
267 std::lock_guard<mutex_t> guard(instance()[i].mut);
268 instance()[i].print_counters();
272 assert(instance() && !is_finalized);
275 std::lock_guard<mutex_t> guard(instance()[i].mut);
276 instance()[i].clean_all_buffers();
281 static void clean_unused_buffers_only() {
282 assert(instance() && !is_finalized);
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));
292 alloc.deallocate(std::get<0>(buffer_tuple), std::get<1>(buffer_tuple));
294 unused_buffer_list.clear();
298#if defined(CPPUDDLE_HAVE_COUNTERS) && defined(CPPUDDLE_HAVE_HPX)
299 static size_t get_sum_number_recycling(
bool reset) {
301 sum_number_recycling = 0;
302 return sum_number_recycling;
304 static size_t get_sum_number_allocation(
bool reset) {
306 sum_number_allocation = 0;
307 return sum_number_allocation;
309 static size_t get_sum_number_creation(
bool reset) {
311 sum_number_creation = 0;
312 return sum_number_creation;
314 static size_t get_sum_number_deallocation(
bool reset) {
316 sum_number_deallocation = 0;
317 return sum_number_deallocation;
319 static size_t get_sum_number_wrong_hints(
bool reset) {
321 sum_number_wrong_hints = 0;
322 return sum_number_wrong_hints;
324 static size_t get_sum_number_wrong_device_hints(
bool reset) {
326 sum_number_wrong_hints = 0;
327 return sum_number_wrong_device_hints;
329 static size_t get_sum_number_bad_allocs(
bool reset) {
331 sum_number_bad_allocs = 0;
332 return sum_number_bad_allocs;
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 "
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 "
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 "
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");
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();
379 throw std::runtime_error(
"Tried allocation after finalization");
381 assert(instance() && !is_finalized);
383 size_t location_id = 0;
385 location_id = *location_hint;
388 throw std::runtime_error(
"Tried to create buffer with invalid location_id [get]");
390 size_t device_id = 0;
392 device_id = *gpu_device_id;
395 throw std::runtime_error(
"Tried to create buffer with invalid device id [get]! "
396 "Is multigpu support enabled with the correct number "
401 std::lock_guard<mutex_t> guard(instance()[location_id].mut);
403 std::list<buffer_entry_type> &unused_buffer_list = instance()[location_id].unused_buffer_map[number_of_elements];
406#ifdef CPPUDDLE_HAVE_COUNTERS
407 instance()[location_id].number_allocation++;
408 sum_number_allocation++;
411 for (
auto iter = unused_buffer_list.begin();
412 iter != unused_buffer_list.end(); iter++) {
414 if (std::get<1>(tuple) == number_of_elements) {
415 unused_buffer_list.erase(iter);
419 if (manage_content_lifetime && !std::get<3>(tuple)) {
420 std::uninitialized_value_construct_n(std::get<0>(tuple),
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;
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++;
432 return std::get<0>(tuple);
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++;
449 if (manage_content_lifetime) {
450 std::uninitialized_value_construct_n(buffer, number_of_elements);
453 }
catch (std::bad_alloc &e) {
456 <<
"Not enough memory left. Cleaning up unused buffers now..."
459 std::cerr <<
"Buffers cleaned! Try allocation again..." << std::endl;
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++;
476 std::cerr <<
"Second attempt allocation successful!" << std::endl;
477 if (manage_content_lifetime) {
478 std::uninitialized_value_construct_n(buffer, number_of_elements);
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) {
489 assert(instance() && !is_finalized);
491 size_t location_id = 0;
493 location_id = *location_hint;
495 throw std::runtime_error(
496 "Buffer recylcer received invalid location hint [mark_unused]");
499 size_t device_id = 0;
501 device_id = *device_hint;
503 throw std::runtime_error(
504 "Buffer recylcer received invalid devce hint [mark_unused]");
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++;
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;
522 assert(std::get<1>(tuple) == number_of_elements);
524 instance()[location_id].unused_buffer_map.at(std::get<1>(tuple)).push_front(tuple);
525 instance()[location_id].buffer_map.erase(memory_location);
529#ifdef CPPUDDLE_HAVE_COUNTERS
530 instance()[location_id].number_wrong_hints++;
531 sum_number_wrong_hints++;
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++;
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;
554 assert(std::get<1>(tuple) == number_of_elements);
556 instance()[location_id].unused_buffer_map.at(std::get<1>(tuple)).push_front(tuple);
557 instance()[location_id].buffer_map.erase(memory_location);
562#ifdef CPPUDDLE_HAVE_COUNTERS
564 sum_number_wrong_device_hints++;
571 if (local_device_id == device_id)
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++;
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;
588 assert(std::get<1>(tuple) == number_of_elements);
590 instance()[location_id].unused_buffer_map.at(std::get<1>(tuple)).push_front(tuple);
591 instance()[location_id].buffer_map.erase(memory_location);
599 if (*location_hint + local_device_id *
max_number_gpus == location_id) {
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++;
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;
614 assert(std::get<1>(tuple) == number_of_elements);
616 instance()[location_id].unused_buffer_map.at(std::get<1>(tuple)).push_front(tuple);
617 instance()[location_id].buffer_map.erase(memory_location);
635 <<
"Warning! Tried to delete non-existing buffer within CPPuddle!"
637 std::cerr <<
"Did you forget to call recycler::finalize?" << std::endl;
642 std::unordered_map<T *, buffer_entry_type> buffer_map{};
644 std::unordered_map<size_t, std::list<buffer_entry_type>> unused_buffer_map{};
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};
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};
661 buffer_manager() =
default;
663 operator=(buffer_manager<T, Host_Allocator>
const &other) =
default;
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{
671 static void init_callbacks_once(
void) {
673#if defined(CPPUDDLE_HAVE_HPX) && defined(CPPUDDLE_HAVE_HPX_MUTEX)
674 static hpx::once_flag flag;
675 hpx::call_once(flag, []() {
677 static std::once_flag flag;
678 std::call_once(flag, []() {
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(
686#ifdef CPPUDDLE_HAVE_COUNTERS
687 buffer_interface::add_print_callback(
692 static inline std::atomic<bool> is_finalized;
694#ifdef CPPUDDLE_HAVE_COUNTERS
695 void print_counters(
void) {
696 if (number_allocation == 0)
701 std::cout <<
"\nBuffer manager destructor for (Alloc: "
702 << boost::core::demangle(
typeid(Host_Allocator).name()) <<
", Type: "
703 << boost::core::demangle(
typeid(T).name())
705 <<
"--------------------------------------------------------------------"
707 <<
"--> Number of bad_allocs that triggered garbage "
709 << number_bad_alloc << std::endl
710 <<
"--> Number of buffers that got requested from this "
712 << number_allocation << std::endl
713 <<
"--> Number of times an unused buffer got recycled for a "
715 << number_recycling << std::endl
716 <<
"--> Number of times a new buffer had to be created for a "
718 << number_creation << std::endl
719 <<
"--> Number cleaned up buffers: "
721 << number_creation << std::endl
722 <<
"--> Number wrong deallocation hints: "
724 << number_wrong_hints << std::endl
725 <<
"--> Number of buffers that were marked as used upon "
727 << buffer_map.size() << std::endl
728 <<
"==> Recycle rate: "
730 <<
static_cast<float>(number_recycling) / number_allocation *
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()) {
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));
751 alloc.deallocate(std::get<0>(buffer_tuple), std::get<1>(buffer_tuple));
753 unused_buffer_list.clear();
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));
761 alloc.deallocate(std::get<0>(buffer_tuple), std::get<1>(buffer_tuple));
764#ifdef CPPUDDLE_HAVE_COUNTERS
765 number_allocation = 0;
766 number_recycling = 0;
767 number_bad_alloc = 0;
769 number_wrong_hints = 0;
781 buffer_manager<T, Host_Allocator>
const &other) =
delete;
783 buffer_manager<T, Host_Allocator> &&other) =
delete;
790 buffer_interface&
operator=(buffer_interface
const &other) =
delete;
792 buffer_interface&
operator=(buffer_interface &&other) =
delete;