30

I'm writing a radix sort algorithm using queues and I would like to have a STL queue allocate space before I start adding things to the queue so that I can avoid constant dynamic resizing operations.

Even though this doesn't exist, I want something with the effect of...

queue<int> qs(N);
for(int i=0;i<N;++i)
  qs.push(rand());

in such a way that it will not dynamically allocate any memory during the loop.

The actual code in question...

void radix_sort()
{
// Biggest number?
int max=-1;
for(int i=0;i<N;++i)
    if(a[i]>max)
        max = a[i];

// How many digits in it
int maxdigits=1;
while(max /= 10) maxdigits++;

// Create some buckets.
deque<int> b[10];
for(int i=0;i<10;++i)
    b[i] = deque<int>(N);

int div=1;
// Radix Sort by digits
for(int d=1;d<=maxdigits;++d)
{
    if(d>1)
        div*=10;

    // Queue
    for(int i=0;i<N;++i)
        b[ (a[i]/div) % 10 ].push_front(a[i]);

    // Dequeue
    int k=0;    
    for(int q=0;q<10;++q)
        while(b[q].size() > 0)
        {
            a[k++] = b[q].back();
            b[q].pop_back();
        }
}
}
Brandon Pelfrey
  • 2,449
  • 6
  • 22
  • 22
  • 1
    It sounds like you don't know if this is a performance problem yet. This is also a very simple example, what kinds of things do you plan on doing to this list? Just push and pop? Does it ever get larger, is it mostly smaller, do you need indexed access? ... – TheJacobTaylor Aug 20 '09 at 04:50
  • 1
    Don't accept your answer so fast, some of us are still just getting to your question. Give it a few hours. – GManNickG Aug 20 '09 at 04:59

7 Answers7

26

Chances are this is not a problem. Deque's allocate in chunks anyway, so you'll probably only reallocate a few times. Have you determined this to be a bottleneck?

Anyway, the standard does not give an accessor to the `queue''s container, because that would defeat the purpose of encapsulation.

If you're really worried, pool allocate. This means preallocate the memory upfront, so when the container asks for memory, it's already there. I can't really go over allocators and kin, that would be overkill for an SO answer, but look up allocators on Google.

Basically, you can tell your container where to get it's memory from. Normally, this is the default allocator, which uses new and delete.

Boost provides a pool allocator, and it would go something like this:

#include <list>
#include <queue>

// pool
#include <boost/pool/pool_alloc.hpp>

// helpful typedef's
typedef boost::fast_pool_allocator<int> BoostIntAllocator;
typedef boost::singleton_pool<boost::fast_pool_allocator_tag, sizeof(int)> BoostIntAllocatorPool;

int main(void)
{
    // specify the list as the underlying container, and inside of that,
    // specify fast_pool_allocator as the allocator. by default, it preallocates
    // 32 elements.
    std::queue<int, std::list<int, BoostIntAllocator > > q;

    /* No memory allocations take place below this comment */

    for (int i = 0; i < 31; ++i)
    {
        q.push(i);
    }

    /* End no allocation */

    // normally, the memory used by the singleton will
    // not be free'd until after the program is complete, 
    // but we can purge the memory manually, if desired:
    BoostIntAllocatorPool::purge_memory();
};

The pool allocates the memory up-front, so no actual memory allocation is done during push()/pop().

I used a list instead of a deque because it is simpler. Normally, a deque is superior to a list, but with an allocator, the things that gave the deque it's advantage, like cache-performance and allocation cost, no longer exist. Therefore, a list is much simpler to use.

You can also use a circular buffer, like such:

#include <queue>

// ring
#include <boost/circular_buffer.hpp>

int main(void)
{
    // use a circular buffer as the container. no allocations take place,
    // but be sure not to overflow it. this will allocate room for 32 elements.
    std::queue<int, boost::circular_buffer<int> > q(boost::circular_buffer<int>(32));

    /* No memory allocations take place below this comment */

    for (int i = 0; i < 31; ++i)
    {
        q.push(i);
    }

    /* End no allocation */
};
Community
  • 1
  • 1
GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • 2
    Define "chunks". I looked at the GCC implementation and `deque` allocates in 512 *byte* chunks. If you think that's bad, MSVC allocates in 16 byte chunks. `deque` implementations are horrible. If you could point out one that isn't, I would be grateful. – doug65536 Jan 23 '14 at 05:22
4

You could try using a std::deque instead...

  • Still kind of new to STL. Thanks for the rather obvious info that I just flat out didn't notice. =] – Brandon Pelfrey Aug 20 '09 at 04:50
  • 1
    Note that deque::push_front() will cause the containers size to grow. You may want to use a vector (with a reserve method) and wrap a std::stack around that... choices choices... –  Aug 20 '09 at 05:00
3

If you use one of the structures to compose your queue that supports the "reserve" function call you should be good. If this data structure does not support your needs, you might want to look for another one.

That being said, are you sure there is a performance problem here?

Jacob

TheJacobTaylor
  • 4,063
  • 1
  • 21
  • 23
  • Yeah the problem is that deque also doesn't let you explicitly reserve space without actually editing the contents. The performance issue is that I'll be pushing to the front and popping the back of the queue, but I want to reserve space for the queue since all of these pushes will cause a dynamic resize and kill my sort performance. – Brandon Pelfrey Aug 20 '09 at 05:03
2

It sounds like you need a data structure with a reserve() method, and efficient "push" and "pop" operations from opposite ends. How about a ring buffer, wrapped around a std::vector ? You could reserve() the space you need in the constructor, then maintain "front" and "end" indices in your implementation to translate "push" and "pop" operations in the public interface to O(1) operations on the underlying std::vector.

Jim Lewis
  • 43,505
  • 7
  • 82
  • 96
1

Instead of a queue, how about use a list instead?

moogs
  • 8,122
  • 8
  • 44
  • 60
  • A list is worse than a deque for a queue, in general. – GManNickG Aug 20 '09 at 04:58
  • errr, a deque doesn't answer the question. a list does (and in fact is almost the same at the marked answer (ring buffer, wrapped around a std::vector). – moogs Aug 20 '09 at 08:14
1

see if this helps: http://www.cs.sunysb.edu/~skiena/392/programs/queue.c

Tam N.
  • 2,687
  • 9
  • 30
  • 29
1

This is a very old question, but I didn’t see an answer to OPs question: How can I use specifically std::queue with a pre-allocated buffer. So here goes:

I’m not aware of a solution that doesn’t require a custom allocator. However if you can use a custom allocator, you can achieve what you’re looking for.

Proof of concept example code is available at https://github.com/vincetse/allocator which I will plagiarize below:

typedef int data_type;
typedef lazy::memory::buffer_allocator<data_type> allocator_type;
typedef std::deque<data_type, allocator_type> deque_type;
typedef std::queue<data_type, deque_type> queue_type;

const size_t buffer_size = 32 * 1024;    
data_type buffer[buffer_size];
allocator_type allocator(buffer, sizeof(buffer));
deque_type d(allocator);
queue_type q(d);
q.push(1);
q.pop();
SomecallmeTim
  • 91
  • 1
  • 2