在堆栈和堆之间动态切换

Dynamically switching between stack and heap

本文关键字:动态 之间 堆栈      更新时间:2023-10-16

假设我正在编写一个简单的缓冲区类。这个缓冲区将充当对象的标准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;
};

正如您所看到的,当缓冲区超过MinSize时,_stack只是浪费了空间。此外,如果Buffer足够大,那么推送和弹出可能会特别昂贵。另一种解决方案是保持前几个元素始终在堆栈中,并将溢出部分放在堆中。但这意味着Buffer无法"转换"为简单的数组。

有更好的解决方案吗?如果这是在std::string中完成的,有人能指出如何或提供一些资源吗?

我建议您使用指针_data而不是_heap,后者总是指代您的数据存储。_heap == NULL会变成_data == _stack,依此类推,但在所有不改变数据长度的情况下,都可以避免区分大小写。

您当前的草图不包括_capacity成员以跟踪当前分配的空间。您需要它来实现"附加到它,如果需要,重新分配"部分,除非您想为堆分配容器的每次长度更改重新分配。

您还可以考虑在数据放入堆栈时不释放堆空间。否则,应用程序可能会在该边界添加和删除单个元素,每次都会导致分配。因此,要么实现一些滞后,要么在分配完堆空间后根本不释放堆空间。一般来说,我认为释放堆内存应该与收缩堆内存同时进行。这两种情况您可能都想自动执行,以响应某个函数调用(如shrink_to_fit),或者根本不执行,但在类似的情况下,执行其中一个而不执行另一个没有什么意义。

除此之外,我相信你的解决方案几乎是你所能期望的。也许为MinSize提供一个默认值。如果MinSize很小,为了避免堆栈溢出,那么浪费空间不会有多大问题,是吗?

当然,最终这一切都取决于您的实际应用程序,因为这种形式的许多未使用的堆栈分配可能会产生不利影响,例如对堆栈内存的缓存。考虑到默认分配器也可能非常智能,您可能应该为给定的应用程序测试您是否真的从这种优化中获得了什么。

我不相信您需要一个新的数据结构。在我看来,您真正想要的是一个新的分配器,用于您认为最好的任何结构。

在C++03中,这将是相对困难的,然而C++11现在要求STL容器与有状态分配器一起工作,因此您可以完美地创建一个具有小堆栈的分配器供自己使用。。。并使用作为CCD_ 11的参数。

示例(使用模板别名)

template <typename T, size_t N = 8>
using SmallVector = std::vector<T, SmallAllocator<T, N>>;

通过这种方式,您将受益于std::vector实现中的所有健壮性和优化,并且您只需提供分配层,这似乎是最初的目标。