Как можно реализовать объект кэша переменного размера, чтобы уменьшить распределение памяти в C ++?

StackOverflow https://stackoverflow.com/questions/4730312

Вопрос

Прежде чем выступление, люди отрывают мне голову: да, я сделал профилирование, прежде чем спросить об этом :)

Я снова смотрю на свой один из контейнера типа, и хотя у меня есть решение, которое работает, производительность плохая, потому что каждый тип элемента, который кэширован, приводит к отдельному распределению на кучу (что, конечно, дорого).

Основываясь на статическом анализе ввода моей программы, я выяснил способ узнать общий размер, требуемый всеми объектами, которые могут быть помещены в мой объект кэша, который передается. По сути, у меня есть список объектов, которые могут быть построены в данном объекте кэша, поэтому я знаю, какой размер того, что мне может придется кэш, заранее, но не во время компиляции - только время выполнения.

По сути, я хотел бы сделать boost::make_shared делает - получает один блок памяти и строит shared_ptr Биты, а также контролируемый объект в том же блоке памяти.

Мне не нужно беспокоиться о сохранении поведения копирования, так как объект кэша не совместимый и передает клиентам (обычно он хранится в чем -то вроде ptr_vector или std::auto_ptr).

Однако я не знаком с тем, как именно можно будет реализовать такой контейнер, а именно то, как человек следует за ограничениями выравнивания и тому подобным.

В псевдокоде, что я хотел бы сделать:

//I know a lot of what's in here is not portable -- I need to run only on x86
//and x64 machines. Yes, this couple of classes looks hacky, but I'd rather
//have one hacky class than a whole programfull :)

class CacheRegistrar
{
    //Blah blah
public:
    //Figures out what objects will be in the cache, etc
    const std::vector<std::size_t>& GetRequiredObjectSizes() const;
    //Other stuff...
    template <typename T>
    void RegisterCacheObject();
    template <typename T>
    std::size_t GetObjectIndex() const;
    // etc.
};

class CacheObject;

std::auto_ptr<CacheObject> CacheObjectFactory(const CacheRegistrar& registrar)
{
    //Pretend this is in a CPP file and therefore CacheObject is defined...
    const std::vector<size_t>& sizes(registrar.GetRequiredObjectSizes());
    std::size_t sumOfCache = std::accumulate(sizes.begin(), sizes.end());
    sumOfCache += sizeof(CacheObject);
    boost::scoped_array<char> buffer(new char[] sumOfCache);
    CacheObject *obj = new (reinterpret_cast<void *>(buffer.get())) CacheObject;
    buffer.release(); //PSEUDOCODE (boost::scoped_array has no release member);
    return std::auto_ptr<CacheObject>(obj); //Nothrow
}

class CacheObject
{
    CacheRegistrar *registrar; //Set by my constructor
public:
    template<typename T>
    T& Get()
    {
        char * startOfCache = reinterpret_cast<char *>(this) + 
            sizeof(CacheObject);
        char * cacheItem = startOfCache + registrar->GetObjectIndex<T>();
        return *reinterpret_cast<T*>(cacheItem);
    }
};

Моя общая концепция звучит здесь? Есть ли лучший способ сделать это?

Это было полезно?

Решение

Но сначала читайте Эта статья Андрея Александреску о том, что он думает, что должен был написать в этой главе - способ создать кучи, используя Слои кучи (вам действительно). Я использовал слои кучи, чтобы построить Клад, Живучи, а также Несгибаемость, а также пользовательские распределители, используемые в нашей бумаге Ooplsa 2002, Пересмотреть пользовательское распределение памяти, который вы также должны прочитать, прежде чем приступить к созданию пользовательского распределения.

Другие советы

Проверьте распределитель малых объектов Loki.

Быстро, что не дало прямых, ориентированных на человека документов. Существует документация, сгенерированная доксигеном, но не особенно Grokkable. Тем не менее, дизайн и реализация задокументированы в «современном дизайне C ++» Андрея Александреску.

Если вы просто хотите эффективную переработку для объектов данного класса, то рассмотрите простой свободный список-возможно, свободный список необработанных кусков.

Ура и hth.,

Ключевой вопрос, который я вижу, - это возвращение

auto_ptr

для памяти, выделенной не по умолчанию. Вы можете решить это, определив подходящую перегруженную удаление, но лучше определить свою собственную функцию уничтожения как часть завода. Если вы сделаете это, вы также локализуете управление памятью в классе кэша, предоставив вам больше свободы для повышения производительности локальной до этого класса. Если, конечно, использование интеллектуального указателя для управления памятью - хорошая идея; Что вам нужно сделать, так это определить свой собственный распределитель и определить SMART_PTR для его использования.

Для справки, еще один подход к управлению пользовательским распределением - это определение нового оператора. Т.е. такие вещи:

struct Cache
{
    void* allocate(size_t size)
    {
        size_t blockSize = sizeof(size_t) + size;
        // Placeholder: do what ever appropriate to blocks of size 'blockSize'
        return malloc(blockSize);
    }
    void destroy(void* p)
    {
        size_t* block = reinterpret_cast<size_t*>(p);
        size_t blockSize = *block;
        // Placeholder: do what ever appropriate to blocks of size 'blockSize'
        free(p);
    }

};
Cache cache;


void* operator new (size_t size, Cache& cache )
{
    return cache.allocate(size);
}

struct CacheObject 
{
    void operator delete(void* p)
    {
        cache.destroy(p);
    }
};


CacheObject* co = new (cache) CacheObject;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top