假设我正在编写一个简单的缓冲类。此缓冲区将充当标准C对象数组的简单包装器。它也应该向后兼容,以使用以简单数组作为输入的现有函数。
此处的目标是使此缓冲区在速度和内存使用方面都高效。由于堆栈分配总是比堆快,我想将堆栈上的所有内容分配到某个阈值,如果它变大,则在堆上重新分配。如何有效地完成这项工作?
我研究过,显然std :: string做了类似的事情。我只是不确定如何。我所拥有的最接近的解决方案是(伪代码,未编译):
template <typename T, int MinSize>
class Buffer
{
public:
void Push(const T& t)
{
++_size;
if (_size > MinSize && _heap == NULL)
{
// allocate _heap and copy contents from stack
// _stack is unused and wasted memory
}
else if (_heap != NULL)
{
// we already allocated _heap, append to it, re-allocate if needed
}
else
{
// still got room on stack, append to _stack
}
}
void Pop()
{
--_size;
if (_size <= MinSize && _heap != NULL)
{
// no need for _heap anymore
// copy values to _stack, de-allocate _heap
}
else if (_heap != NULL)
{
// pop from heap
}
else
{
// pop from stack
}
}
private:
T _stack[MinSize];
T* _heap;
int _size;
};
如您所见,当缓冲区增长超过_stack
时,MinSize
只是浪费空间。此外,如果Buffer足够大,推送和弹出可能会特别昂贵。另一个解决方案是将前几个元素保持在堆栈上,并将溢出放在堆上。但这意味着缓冲区无法“转换”为简单数组。
有更好的解决方案吗?如果这是在std :: string中完成的,那么有人可以指出如何或提供一些资源吗?
答案 0 :(得分:3)
我建议您使用指针_data
而不是_heap
,它总是引用您的数据存储。 _heap == NULL
将成为_data == _stack
等等,但在所有不会影响数据长度的情况下,您可以避免区分大小。
您当前的草图不包含_capacity
成员以跟踪当前分配的空间。你需要它来实现“追加它,在需要时重新分配”部分,除非你想为堆分配容器的每一个长度变化重新分配。
当数据适合堆栈时,您可能还会考虑不释放堆空间。否则,应用程序可能会在该边界添加和删除单个元素,每次都会导致分配。因此,在分配之后,要么实现一些hysteresis,要么根本不释放堆空间。一般来说,我会说释放堆内存应该与缩减堆内存一起使用。这两个你可能想要自动地做,以响应某个函数调用,如shrink_to_fit
,或者根本不做,但是在一个类似的情况下做一个而不是另一个没什么意义。
除此之外,我相信您的解决方案几乎是您所希望的。也许为MinSize
提供默认值。如果MinSize
很小,为了避免堆栈溢出,那么浪费那个空间不会有太大的问题,是吗?
当然,最终这一切都取决于您的实际应用,因为此表单的大量未使用的堆栈分配可能会产生负面影响,例如:关于堆栈内存的缓存。鉴于默认分配器也可以非常智能,您可能应该对给定应用程序中是否实际获得此优化的任何内容进行基准测试。
答案 1 :(得分:2)
我不相信你需要一个新的数据结构。在我看来,你真正想要的是一个新的分配器,用于你认为最好的任何结构。
在C ++ 03中,这可能相对困难,但是C ++ 11现在要求STL容器使用有状态分配器,因此您可以完美地创建一个具有小堆栈的分配器供自己使用......使用 作为std::vector<>
的参数。
示例(使用模板别名)
template <typename T, size_t N = 8>
using SmallVector = std::vector<T, SmallAllocator<T, N>>;
通过这种方式,您将受益于std::vector
实施的所有稳健性和优化,您只需提供分配层,这似乎是最初的目标。