Open apolukhin opened 3 years ago
Victor Gubin, 9 октября 2017, 13:32 То что "вылетело" из секции например, и в чем был весь смысл:
#include <jemalloc/jemalloc.h>
#include <windows.h>
namespace jemalloc {
struct memory_traits {
static void* allocate(std::size_t size) {
void *ret = nullptr;
// some while(nullptr == __builtin_expect( (ret == std::malloc) ,false) ) {
while ( nullptr == (ret = je_malloc(size)) ) {
std::new_handler handler = std::get_new_handler();
if(nullptr == handler)
throw std::bad_alloc();
handler();
}
return ret;
}
static void* allocate(std::size_t size, const std::nothrow_t&) noexcept {
void *ret = nullptr;
// some while(nullptr == __builtin_expect( (ret == std::malloc) ,false) ) {
while ( nullptr == (ret = ::je_malloc(size)) ) {
std::new_handler handler = std::get_new_handler();
if(nullptr == handler)
return nullptr;
handler();
}
return ret;
}
static void release(void* const ptr) noexcept {
::je_free( ptr );
}
};
} // jemalloc
namespace windows {
class memory_traits {
public:
static void* allocate(std::size_t size) {
void *ret = nullptr;
// use some __assume
while ( nullptr == (ret = ::HeapAlloc( instance()->heap_, 0, size) ) ) {
std::new_handler handler = std::get_new_handler();
if(nullptr == handler)
throw std::bad_alloc();
handler();
}
}
static void* allocate(std::size_t size, const std::nothrow_t&) noexcept {
void *ret = nullptr;
// use some __assume
while ( nullptr == (ret = ::HeapAlloc( instance()->heap_, 0 , size ) ) ) {
std::new_handler handler = std::get_new_handler();
if(nullptr == handler) {
errno = ENOMEM;
return nullptr;
}
handler();
}
return ret;
}
static void release(void* const ptr) noexcept {
::HeapFree(instance()->heap_, 0, const_cast<LPVOID>(ptr) );
}
private:
static const memory_traits* instance() noexcept {
static memory_traits _instance( ::HeapCreate(0,0,0) );
return &_instance;
}
constexpr explicit memory_traits(::HANDLE heap) noexcept:
heap_(heap)
{}
::HANDLE heap_;
};
} // namesapce windows
...
void main() {
// std::allocator ( new/delete in most cases )
std::vector<int> v;
// red-black map with je_malloc nodes
std::set< int, std::less<int>, std::basic_allocator<int, jemalloc::memory_traits > > s;
// hash table with windows private heap allocator for buckets
typedef std::unordered_map<
int, std::string
std::hash<int>,
std::equal_to<int>,
std::basic_allocator< std::pair<int,std::string>, windows::memory_traits>
> int_hash_map;
}
Will Code For Food, 25 октября 2017, 14:08 Это же std::pmr::polymorphic_allocator и std::pmr::memory_resource, не?
Виктор Губин, 18 марта 2019, 17:31 Will Code For Food,
Несовсем, std::pmr::polymorphic_allocator/std::pmr::memory_resource - обладают очень нежелательным свойством. get_default_resource/set_default_resource
Теперь положим, у меня в программе есть пул из N потоков. Есть контейнер с бинарной кучей - priority queue, он разделятеся между всеми потоками и синхронизируется. Остальные потоки имеют множество других контейнеров, которые с специфичными аллокаторами (отдельный для строк, деревьев и хеш таблиц). Memory resource при этом глобальный (даже если он будет thread_local это не сильно поможет, если контейнеров с "хитрыми" аллокаторами более одного).
Перенос предложения: голоса +0, -3 Автор идеи: Victor Gubin
Мотивация. STL контейнеры позволяют определить собственный распределитель памяти, и принимают std::allocator в качестве параметра по умолчанию. Если нужно использовать распределитель памяти, отличный от используемого std::allocator - следует реализовать собственный шаблон Allocator, как правило путем копирования кода std::allocator и внесением незначительных (в объеме кода) изменений.
В большинстве случаев создание не стандартной реализации шаблона allocator, связанно с необходимостью заменить вызовы к базовом операторам new и delete. Это необходимо для замены универсального алгоритма распределения памяти используемого стандартной библиотекой, и не подходящего для конкретного случая. При этом глобально переопределять std::new и std::delete нежелательно.
В тоже время, шаблон allocator весьма сложный класс, и все STL контейнеры требуют четкого соблюдения интерфейса Allocator от любой не стандартной реализации. Таким образом, для замены обращений к стандартному распределителю памяти, на более подходящий для конкретного контейнера, нужно посути продублировать код std::allocator в собственной реализации, изменив всего несколько строк кода.
Концепция memory_traits
memory_traits это небольшой proxy интерфейс над базовым распределителем памяти, который просто реализуем и может быть передан базовому шаблону allocator.
Например для обращения к глобальным операторам new и delete memory_traits будет выглядеть следующим образом
Тогда в стандартной библиотеке можно ввести следующий шаблон
И спецификацию std::allocator можно определить как ( если это нужно ).
Таким образом, можно легко заменять std::allocator для любого контейнера, потребуется только реализовать memory_traits и передать его шаблону std::basic_allocator.
Например:
Введение memory_traits и std::basic_allocator затронут уже существующий код. Но определить Allocator для конкретного контейнера станет намного проще.