2009-08-20 14 views
22

Sto scrivendo un algoritmo di ordinamento radix utilizzando le code e vorrei che una coda STL allocasse spazio prima di iniziare ad aggiungere elementi alla coda in modo da evitare operazioni di ridimensionamento dinamico costante.Pre-allocare spazio per coda C++ STL

Anche se questo non esiste, voglio qualcosa con l'effetto di ...

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

in modo tale che non allocare dinamicamente la memoria durante il ciclo.

Il codice effettivo in questione ...

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(); 
     } 
} 
} 
+1

Sembra che non si sa se questo è ancora un problema di prestazioni. Questo è anche un esempio molto semplice, che tipo di cose pensi di fare su questa lista? Basta spingere e pop? Diventa sempre più grande, è per lo più più piccolo, hai bisogno di un accesso indicizzato? ... – TheJacobTaylor

+1

Non accettare la tua risposta così in fretta, alcuni di noi stanno ancora rispondendo alla tua domanda. Dagli qualche ora. – GManNickG

risposta

24

Le probabilità sono che questo non sia un problema. L'allocazione di Deque avviene comunque in blocchi, quindi sarà possibile riallocarne solo poche volte. Hai determinato che questo è un collo di bottiglia?

Ad ogni modo, lo standard non fornisce un accessor al contenitore `queue ', perché ciò vanificherebbe lo scopo dell'incapsulamento.

Se sei davvero preoccupato, il pool si alloca. Ciò significa preallocare la memoria in anticipo, quindi quando il contenitore richiede memoria, è già lì. Non riesco a superare gli allocatori e parenti, sarebbe eccessivo per una risposta SO, ma cercare allocators on Google.

Fondamentalmente, puoi dire al tuo contenitore da dove ottenere la memoria. Normalmente, questo è l'allocatore predefinito, che usa new e delete.

Boost fornisce un pool allocator, e sarebbe più o meno così:

#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(); 
}; 

La piscina alloca la memoria up-front, quindi non allocazione di memoria effettiva è fatto durante push()/pop().

Ho utilizzato uno list anziché uno deque perché è più semplice. Normalmente, un deque is superior to a list, ma con un allocatore, le cose che davano il vantaggio di deque, come il rendimento della cache e il costo di allocazione, non esistono più.Pertanto, un list è molto più semplice da usare.

È inoltre possibile utilizzare un circular buffer, come ad esempio:

#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 */ 
}; 
+0

+1! Non sono degno !!!

+2

Definisci " chunks. "Ho esaminato l'implementazione GCC e' deque' alloca in blocchi di 512 * byte * Se pensi che sia male, MSVC alloca in blocchi di 16 byte. Le implementazioni di 'deque' sono orribili.Se potresti indicarne una che non è t, sarei grato. – doug65536

2

Se si utilizza una delle strutture per comporre la coda che supporta la funzione di "riserva" Chiamata si dovrebbe essere buono. Se questa struttura dati non supporta le tue esigenze, potresti volerne cercare un'altra.

Detto questo, sei sicuro che ci sia un problema di prestazioni qui?

Jacob

+0

Sì, il problema è che Deque non ti lascia esplicitamente riservare spazio senza effettivamente modificare i contenuti. Il problema delle prestazioni è che spingerò in prima fila e facendo scattare il retro della coda, ma voglio riservare spazio per la coda poiché tutti questi push provocheranno un ridimensionamento dinamico e interromperanno le mie prestazioni di ordinamento. –

3

Si potrebbe provare a utilizzare uno std :: deque invece ...

+0

Ancora un nuovo tipo di STL. Grazie per le informazioni piuttosto ovvie che non ho notato. =] –

+1

Si noti che deque :: push_front() farà aumentare la dimensione dei contenitori. Potresti voler usare un vettore (con un metodo di riserva) e avvolgere uno std :: stack attorno a ... scelte scelte ... –

1

Invece di una coda, che ne dite di usare una lista, invece?

+0

Una lista è peggio di una coda per una coda, in generale. – GManNickG

+0

errr, un dequio non risponde alla domanda. una lista fa (ed infatti è quasi la stessa alla risposta contrassegnata (ring buffer, avvolto attorno a un vettore std.) – moogs

2

Sembra che tu abbia bisogno di una struttura di dati con un metodo di riserva(), e "push" efficiente e le operazioni "pop" da estremità opposte. Che ne dici di un buffer ad anello, avvolto attorno a un vettore std ::? È possibile riservare() lo spazio necessario nel costruttore, quindi mantenere indici "frontali" e "finali" nell'implementazione per tradurre operazioni "push" e "pop" nell'interfaccia pubblica in operazioni O (1) sullo std sottostante ::vettore.

+0

Suoni promettenti. Proverò presto. Grazie! –

+0

Sono d'accordo, stavo per suggerire un oggetto semplice contenente ptr testa/coda e un gruppo di oggetti liberi.Dovrebbe quindi creare e mantenere una lista doppiamente collegata.Il tuo metodo è più semplice. – TheJacobTaylor