Какой тип данных использовать для буфера пользовательского распределителя на основе стека? - PullRequest
0 голосов
/ 18 ноября 2018

Я хочу создать свой собственный игровой движок, поэтому я купил несколько книг, одна из которых Game Engine Architecture, второе издание от Джейсон Грегори , и в нем он предлагает реализовать несколько пользовательских распределителей.Одним из типов распределителя, о котором говорилось в книге, был распределитель на основе стека, но я запутался, читая его.Как вы храните данные в нем?Какой тип данных вы используете?Например, вы используете void*, void**, массив char[]?В книге говорится, что вы должны выделить один большой блок памяти, используя malloc в начале, и освободить его в конце, и «выделить» память, увеличив указатель.Если бы вы могли помочь объяснить это более, это было бы здорово, потому что я не могу найти учебник, который не использует std :: allocator.Я также подумал, что это может помочь другим, заинтересованным в пользовательском распределителе, поэтому я разместил вопрос здесь.

Это пример файла заголовка, который они приводят в книге:

class StackAllocator
{
 public:
    // Represents the current top of the stack.
    // You can only roll back to the marker not to arbitrary locations within the stack
    typedef U32 Marker;

    explicit StackAllocator(U32 stackSize_bytes);

    void* alloc(U32 size_bytes); // Allocates a new block of the given size from stack top
    Marker getMarker(); // Returns a Marker to the current stack top
    void freeToMarker(Marker marker); // Rolls the stack back to a previous marker
    void clear(); // Clears the entire stack(rolls the stack back to zero)

private:
    // ...
}

РЕДАКТИРОВАТЬ: Через некоторое время у меня все заработало, но я не знаю, правильно ли я это делаю

Файл заголовка

typedef std::uint32_t U32;

struct Marker {
    size_t currentSize;
};

class StackAllocator
{
private:
    void* m_buffer; // Buffer of memory
    size_t m_currSize = 0;
    size_t m_maxSize;

public:
    void init(size_t stackSize_bytes); // allocates size of memory
    void shutDown();

    void* allocUnaligned(U32 size_bytes);

    Marker getMarker();
    void freeToMarker(Marker marker);

    void clear();
};

Файл .cpp

void StackAllocator::init(size_t stackSize_bytes) {
    this->m_buffer = malloc(stackSize_bytes);
    this->m_maxSize = stackSize_bytes;
}

void StackAllocator::shutDown() {
    this->clear();

    free(m_buffer);
    m_buffer = nullptr;
}

void* StackAllocator::allocUnaligned(U32 size_bytes) {
    assert(m_maxSize - m_currSize >= size_bytes);

    m_buffer = static_cast<char*>(m_buffer) + size_bytes;
    m_currSize += size_bytes;
    return m_buffer;
}

Marker StackAllocator::getMarker() {
    Marker marker;
    marker.currentSize = m_currSize;
    return marker;
}

void StackAllocator::freeToMarker(Marker marker) {
    U32 difference = m_currSize - marker.currentSize;
    m_currSize -= difference;
    m_buffer = static_cast<char*>(m_buffer) - difference;
}

void StackAllocator::clear() {
    m_buffer = static_cast<char*>(m_buffer) - m_currSize;
}

1 Ответ

0 голосов
/ 18 ноября 2018

Хорошо, для простоты, скажем, вы отслеживаете коллекцию MyFunClass для вашего движка.Это может быть что угодно, и ваш линейный распределитель не обязательно должен отслеживать объекты однородного типа, но часто это так.В целом, при использовании пользовательских распределителей вы пытаетесь «сформировать» свои распределения памяти для разделения статических данных от динамических, редко используемых и часто используемых с целью оптимизации вашего рабочего набора и достижения локальности ссылок.

Учитывая код, который вы указали, сначала вы должны выделить свой пул памяти.Для простоты предположим, что вам нужно достаточно места для объединения 1000 объектов типа MyFunClass.

StackAllocator sa;
sa.Init( 1000 * sizeof(MyFunClass) );

Тогда каждый раз, когда вам нужно «выделить» новый блок памяти для FunClass, вы можете сделатьэто так:

void* mem = sa.allocUnaligned( sizeof(MyFunClass) );

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

Он также не создает MyFunClass.Ваш распределитель не является строго типизированным, поэтому память, которую он возвращает, может быть интерпретирована , как вы хотите : как поток байтов;в качестве вспомогательного представления объекта класса C ++;и т.д.

Теперь, как бы вы использовали буфер, выделенный таким образом?Одним из распространенных способов является размещение нового:

auto myObj = new (mem) MyFunClass();

Итак, теперь вы создаете свой объект C ++ в том пространстве памяти, которое вы зарезервировали с помощью вызова allocUnaligned.

(обратите внимание, чтоallocUnaligned bit дает вам некоторое представление о том, почему мы обычно не пишем наши собственные пользовательские распределители: потому что их сложно получить до чертиков! Мы еще даже не упомянули проблемы выравнивания.)

Длядополнительные кредиты, взгляните на стеки областей действия , которые выводят подход линейного распределителя на следующий уровень.

...