6

Suppose I'm writing a simple buffer class. This buffer would act as a simple wrapper for a standard C array of objects. It should also be backwards-compatible to work with existing functions that take simple arrays as input.

The goal here is to make this buffer efficient in both speed and memory usage. Since stack allocation is always faster than heap, I want to allocate everything on a stack to a certain threshold, and if it grows larger, re-allocate on the heap. How can this be done efficiently?

I researched, and apparently std::string does something similar. I'm just not sure how. The closest solution I've had has been something along the lines of (pseudo-code, not compiled):

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;
};

As you can see, _stack is simply wasted space when the buffer grows beyond MinSize. Also, push and pop can be especially costly if Buffer is large enough. Another solution was to keep the first few elements always on stack, and put the overflow on heap. But that would mean the Buffer could not be 'converted' to a simple array.

Is there a better solution? If this is done in std::string, could anybody point out how or provide some resources?

Zeenobit
  • 4,954
  • 3
  • 34
  • 46
  • 1
    `std::string` has a pointer that it dynamically allocates memory for. There's no actual data on the stack ever; it's all on the heap. Frankly, I'm not sure why you don't want to just put it on the heap in the first place. What happens when the size gets almost too large for the stack and then something else causes a stack overflow? – chris Sep 09 '12 at 05:35
  • @chris If `MinSize` is small, the chances of a stack overflow (Ha!) would be rather small (unless the objects are really big, which shouldn't be the case). There would be performance boosts if small Buffers work on stack memory. So I want to know if it's possible to have the best of both worlds, so to speak. – Zeenobit Sep 09 '12 at 05:40
  • 3
    @chris Some implementations of std::string don't use the heap for small strings as the OP says. Try googling for 'short string optimization'. – john Sep 09 '12 at 05:41
  • Hm, I could've sworn it was in the standard, but I don't see anything in there right now. I agree that with a low enough `MinSize`, it might be worthwhile (although it would be worth checking the speed difference imo), and those short strings are pretty interesting. It's even [right here](http://stackoverflow.com/questions/1466073/how-is-stdstring-implemented) on SO. – chris Sep 09 '12 at 05:47
  • @john Thanks for the tip. I came across this article: http://john-ahlgren.blogspot.ca/2012/03/small-string-optimization-and-move.html But that one simply uses the method I described above. I guess that's the closest it gets. – Zeenobit Sep 09 '12 at 05:54
  • @FarbodT. The only slight change I'd recommend is to use an anonymous union for your stack and heap members. It will save you a few bytes. I've just looked at Microsoft's string implementation and that's how they do it. Incidentally in their code char strings are 'short' if the buffer size <= 16. Any bigger and they start using the heap. – john Sep 09 '12 at 05:59
  • @john Yah, the more I research, the more I see that they all use the same method. I guess it makes sense. If `MinSize` is small, and the actual size is large, the wasted space is a small percentage value. Still, thanks for pointing me to Small String Optimization. If you want, reply with an answer so I can give credit. :) – Zeenobit Sep 09 '12 at 07:06
  • I'd use minsize as size in bytes. You can use `minsize/sizeof T` as array size, thus it would safer regarding stack usage. For dynamic array I'd use `std::vector` – PiotrNycz Sep 09 '12 at 07:08
  • @FarbodT. The implementation Microsoft uses is not uncommon. We used similar standards for string management on a significantly sized project. We didn't just choose some number out of thin air, however. I ran substantial coverage assessment to determine a >50% hit ratio to avoid heap allocs, and as it turns out, it was only about 4k. yeah, 4K is huge compared to 16 chars/wchar_ts, but we had plenty of stack space and I can't do justice to the performance increase and reduction in heap fragmentation. – WhozCraig Sep 09 '12 at 07:13
  • 1
    @FarbodT.: Your approach is not so far from LLVM's [SmallVector](http://llvm.org/docs/doxygen/html/SmallVector_8h_source.html), which presents a similar interface. However... perhaps that you are mixing requirements here. – Matthieu M. Sep 09 '12 at 12:47

2 Answers2

3

I would suggest you use a pointer _data instead of _heap, which always refers to your data store. _heap == NULL would become _data == _stack and so on, but in all situations which don't chanmge the length of the data, you could avoid the case distinction.

Your current sketch doesn't include a _capacity member to keep track of the currently allocated space. YOu'll need that to implement the “append to it, re-allocate if needed” part, unless you want to reallocate for each and every length change of a heap-allocated container.

You might also consider not freeing the heap space the moment your data fits onto the stack. Otherwise there might be applications adding and removing a single element just at that boundary, causing an allocation each time. So either implement some hysteresis or don't free the heap space at all once you've allocated it. In general I'd say freeing heap memory should go together with shrinking heap memory. Both of these you might want to do either automatically, in response to a certain function call like shrink_to_fit, or not at all, but there is little point in doing one but not the other in a similar situation.

Apart from this, I believe your solution is pretty much all you can hope for. Perhaps provide a default value for MinSize. If MinSize is small, to avoid stack overflows, then wasting that space isn't going to be much of a problem, is it?

Of course, in the end it all depends on your actual application, as a lot of unused stack allocations of this form might have an adverse impact e.g. on the caching of stack memory. Given the fact that default allocators can be pretty smart as well, you probably should benchmark whether you actually gain anything from this optimization, for a given application.

MvG
  • 57,380
  • 22
  • 148
  • 276
2

I am not convinced that you need a new data structure here. It seems to me that you really want is a new allocator, to be used with whatever structure you think is best.

In C++03, this would have been relatively difficult, however C++11 now requires that STL containers work with stateful allocators, so you could perfectly create an allocator with a small stack for its own use... and use that as an argument to std::vector<>.

Example (using template aliases)

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

This way you'll benefit from all the robustness and optimizations that went into the implementation of std::vector, and you'll just provide the allocation layer, which was the goal initially, it seems.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722