自定义基于堆栈的分配器的缓冲区使用什么数据类型?
What data type to use for the buffer of a custom stack-based allocator?
我想创建自己的游戏引擎,所以我买了几本书,其中一本是 Game Engine Architecture Second Edition Jason Gregory 和他在其中建议实施一些自定义分配器。书中谈到的一种分配器是基于堆栈的分配器,但我在阅读时感到困惑。你如何在其中存储数据?你使用什么数据类型?比如你用的是void*
、void**
、char[]
的数组吗?这本书说你打算在开始时使用 malloc 分配一大块内存,最后释放它,并通过递增指针来分配 "allocate" 内存。如果你能帮助解释更多,那就太好了,因为我似乎找不到不使用 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;
}
好吧,为简单起见,假设您正在为您的引擎跟踪 MyFunClass
的集合。它可以是任何东西,你的线性分配器不一定要跟踪同质类型的对象,但通常就是这样完成的。通常,在使用自定义分配器时,您会尝试 "shape" 内存分配以将静态数据与动态数据、不经常访问的数据和经常访问的数据分开,以优化您的工作集并实现引用的局部性。
根据您提供的代码,首先,您将分配您的内存池。为简单起见,假设您需要足够的 space 来池化 1000 个 MyFunClass
.
类型的对象
StackAllocator sa;
sa.Init( 1000 * sizeof(MyFunClass) );
然后每次你需要 "allocate" 一个新的内存块用于 FunClass
,你可以这样做:
void* mem = sa.allocUnaligned( sizeof(MyFunClass) );
当然,这实际上并没有分配任何东西。所有分配都已在步骤 1 中进行。它只是将一些已分配的内存标记为正在使用。
它也没有构建 MyFunClass
。你的分配器不是强类型的,所以它returns的内存可以任何你想要的解释:作为字节流;作为 C++ class 对象的后备表示;等等
现在,您将如何使用以这种方式分配的缓冲区?一种常见的方法是放置 new:
auto myObj = new (mem) MyFunClass();
所以现在您正在通过调用 allocUnaligned
.
保留的内存 space 中构建 C++ 对象
(请注意,allocUnaligned
位让您深入了解为什么我们通常不编写自己的自定义分配器:因为它们很难做到正确!我们甚至没有提到对齐问题还没有。)
要获得额外的学分,请查看 scope stacks,它将线性分配器方法提升到一个新的水平。
我想创建自己的游戏引擎,所以我买了几本书,其中一本是 Game Engine Architecture Second Edition Jason Gregory 和他在其中建议实施一些自定义分配器。书中谈到的一种分配器是基于堆栈的分配器,但我在阅读时感到困惑。你如何在其中存储数据?你使用什么数据类型?比如你用的是void*
、void**
、char[]
的数组吗?这本书说你打算在开始时使用 malloc 分配一大块内存,最后释放它,并通过递增指针来分配 "allocate" 内存。如果你能帮助解释更多,那就太好了,因为我似乎找不到不使用 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;
}
好吧,为简单起见,假设您正在为您的引擎跟踪 MyFunClass
的集合。它可以是任何东西,你的线性分配器不一定要跟踪同质类型的对象,但通常就是这样完成的。通常,在使用自定义分配器时,您会尝试 "shape" 内存分配以将静态数据与动态数据、不经常访问的数据和经常访问的数据分开,以优化您的工作集并实现引用的局部性。
根据您提供的代码,首先,您将分配您的内存池。为简单起见,假设您需要足够的 space 来池化 1000 个 MyFunClass
.
StackAllocator sa;
sa.Init( 1000 * sizeof(MyFunClass) );
然后每次你需要 "allocate" 一个新的内存块用于 FunClass
,你可以这样做:
void* mem = sa.allocUnaligned( sizeof(MyFunClass) );
当然,这实际上并没有分配任何东西。所有分配都已在步骤 1 中进行。它只是将一些已分配的内存标记为正在使用。
它也没有构建 MyFunClass
。你的分配器不是强类型的,所以它returns的内存可以任何你想要的解释:作为字节流;作为 C++ class 对象的后备表示;等等
现在,您将如何使用以这种方式分配的缓冲区?一种常见的方法是放置 new:
auto myObj = new (mem) MyFunClass();
所以现在您正在通过调用 allocUnaligned
.
(请注意,allocUnaligned
位让您深入了解为什么我们通常不编写自己的自定义分配器:因为它们很难做到正确!我们甚至没有提到对齐问题还没有。)
要获得额外的学分,请查看 scope stacks,它将线性分配器方法提升到一个新的水平。