2010-11-17 17 views
5

Ho un piccolo problema e non riesco a trovare una soluzione soddisfacente per questo. C'è un array di byte e ho bisogno di questi byte ordinati per 7 bit alti mentre preservando l'ordine dei bit bassi.Ordinamento di tipo di matrice di byte rapido

Così in origine si presentava così:

// sort buf[N] to tmp[N] 
uint offs[128+1]; uint c,i,s; 
for(i=0; i<128; i++) offs[i]=0; 
for(i=0; i<l; i++) offs[buf[i]>>1]++; 
for(i=0,s=0; i<128; i++) c=offs[i], offs[i]=s, s+=c; offs[i]=s; 

byte* tmp = new byte[N]; 
for(i=0; i<N; i++) c=buf[i], tmp[offs[c>>1]++]=c; // sort 

Ma questi blocchi sono abbastanza grandi (8M attualmente), e voglio usare più thread, ed un 8M extra per filo è evidente.

così ho cercato di utilizzare alcuni semplici radix sort:

void radix(byte* buf, uint h, uint l, uint mask) { 
    uint p = (h+l)>>1, q = h; 
    uint i = offs[h], j = offs[l]-1; h = offs[p]; 
    if((i<h) && (j>=h)) { 
    byte c = buf[i], d = buf[j]; 
    while((i<h) && (j>=h)) { 
     while((c&mask)==0) c = buf[++i]; // find value with bit 1 
     while((d&mask)!=0) d = buf[--j]; // find value with bit 0 
     buf[i]=d; buf[j]=c; // swap 1-0 -> 0-1 
     c = buf[++i]; d = buf[--j]; 
    } 
    if(mask>=4) { 
     radix(buf, q,p, mask>>1); 
     radix(buf, p,l, mask>>1); 
    } 
    } 
} 

ma cambia l'ordine di questi bit bassi e diventa inutilizzabile.

In realtà alcuni metodi più semplici, come bubblesort, fanno semplicemente ciò che voglio, ma sono molto più lenti e anche la velocità è un problema.

Così attualmente I sorta blocchi più piccoli tramite un buffer temporaneo, quindi utilizzare una tabella indice per accedere blocchi parzialmente ordinati in ordine:

struct tmpsort { 

    enum{ blocksize = (1<<16)-1 }; 

    unsigned short ofs[(max_quants+blocksize-1)/blocksize][probN]; 

    tmpsort(byte* buf, uint f_len) { 
    uint i,j,k; 
    uint freq[2*probN]; // prob freqs 
    byte tmp[blocksize+1]; 

    for(k=0,j=0; k<f_len; k+=blocksize,j++) { 
     uint l = Min(k+blocksize,f_len)-k; 
     byte* p = &buf[k]; 

     // compute offsets of sorted chunks 
     for(i=0; i<2*probN; i++) freq[i]=0; 
     for(i=0; i<l; i++) freq[p[i]]++; 
     for(i=0; i<probN; i++) freq[i+1]=freq[2*i+0]+freq[2*i+1]; // 1=0+1, 2=2+3, 3=4+5 
     freq[0] = 0; 
     for(i=0; i<probN; i++) freq[i+1]+=freq[i]; 
     for(i=0; i<probN; i++) ofs[j][i]=freq[i+1]; 

     // sort the block via tmp 
     for(i=0; i<l; i++) { byte c=p[i]; tmp[freq[c>>1]++]=c; } 
     for(i=0; i<l; i++) p[i]=tmp[i]; 
    } 
    } 

}; 

[...] 

tmpsort ts(buf, f_len); 
for(i=0; i<probN; i++) { 
    for(k=0,j=0; k<f_len; k+=ts.blocksize,j++) { 
    uint x = i>0 ? ts.ofs[j][i-1] : 0; 
    for(; x<ts.ofs[j][i]; x++) putc(buf[k+x],g); 
    } 
} 

Ma tmp [] e OFS [] array utilizzano troppo spazio di stack e il suo non è un tipo completo, quindi continuo a chiedermi se c'è qualche soluzione pulita per questo.

Un campione di dati e le mie realizzazioni sono disponibili qui: http://nishi.dreamhosters.com/u/tmpsort_v0.rar

risposta

0

Avendo 64kB aggiuntivi, è possibile (come si è notato) memorizzare un blocco di 512 kbit (meno una certa quantità fissa di dati di indicizzazione) in forma compressa (memorizzando solo i bit più bassi per ogni chiave) Passare sopra i blocchi grandi e convertire li alle loro forme compresso-ordinati, compattandoli mentre andate all'inizio dell'intero array.

Ora unire i moduli compressi in un unico grande modulo compresso (facile con il 7M liberato.) Quindi decomprimere di nuovo alla matrice ordinata.

Questo è O (N), anche se la costante sembra piuttosto grande con 3 passaggi che comportano alcune operazioni bit non banali.

+0

Grazie, ho davvero perso questo approccio, potrebbe valere la pena provare. – Shelwien

1

Perché non basta usare qualsiasi standard sul posto, stabilesorting algorithm, per esempio Insertion Sort e implementare una funzione di confronto appropriata?

+0

la soluzione con due buffer richiede N letture e N scritture. Qui ho bisogno di qualcosa di veloce, e le implementazioni di ordinamento standard non sono intese per l'ordinamento dei byte. – Shelwien

0

È possibile implementare quicksort come ordinamento stabile. In termini di big-O, non è migliore dell'insertion sort, ma in pratica eseguirà un lotto . Se installi le reti di ordinamento per le dimensioni del foglio fino a 6 o 8, penso che si tratti delle migliori prestazioni che otterrai per un ordinamento stabile e sul posto.

In realtà ... presumibilmente esiste una sorta di un tipo di fusione stabile e sul posto. In termini di caratteristiche teoriche ideali, è il Santo Graal del sorting - in-place, vero O(n log n), e stabile, tutto nello stesso tempo. Ma sospetto che sia un enorme dolore da implementare e abbia termini piuttosto grandi per andare con quel Big-O.

+0

Penso sia molto importante che ci siano solo 128 chiavi diverse qui. Inoltre ho preso in considerazione l'implementazione di un mergesort bit per bit qui (0 (10) 1 -> 0011 tramite xy = reverse (reverse (y) + reverse (x))), ma sembra così lento rispetto a quel loop a una linea. – Shelwien

+0

Btw, occorrono 15.610 per elaborare un file 100M utilizzando la prima versione con buffer aggiuntivo e 17.594 con "tmpsort" sopra – Shelwien

+0

Sì, ma quei bit bassi che si desidera mantenere in ordine sono ancora molte informazioni; tenerli non sarà gratuito. Se non ti dispiace usare un buffer di output separato, ho un algoritmo veloce che pubblicherò come un'altra risposta. –

1

Questo può essere eseguito con un codice relativamente semplice in un po 'più di tempo O (n log n) utilizzando una versione di ordinamento digitale che esegue un ordinamento stabile su ciascuno dei 7 bit importanti, da quelli meno significativi a quelli più significativi. Il vantaggio di questa tecnica rispetto ad un ordinamento di unione stabile è che il codice è molto più semplice se si scrive tutto da solo.

Questa è la funzione per eseguire un ordinamento stabile sul posto di un bit specificato. Qui, è scritto in modo ricorsivo per semplicità utilizzando O (lg n) spazio di stack (questo utilizzo dello spazio di stack può essere eliminato se si desidera utilizzando un ciclo for per organizzare il divide et impera approccio):

// sort array x from i to j by bit b 
sort(x, i, j, b) { 
    if (i >= j - 1) return; 
    mid = (i + j)/2; 
    sort(x, i, mid, b); 
    sort(x, mid, j, b); 
    first1 = -1; 
    last0 = -1; 
    for (k = i; k < j; k++) { 
    if (first1 < 0 && isSet(x[k], b)) first1 = k; 
    if (!isSet(x[k], b)) last0 = k; 
    } 
    if (last0 < first1) return; 

    // the sequence of bit b generally looks something like 0000011100000111111 
    // so we reverse from the first 1 to the last 0 
    reverse(x, first1, last0afterfirst1); 
    newlast0 = first1; 
    while (!isSet(x[++newlast0], b)); 
    newlast0--; 

    // the elements in the range first1..last0 are in the wrong order, so reverse 
    reverse(x, first1, newlast0); 
    reverse(x, newlast0 + 1, last0); 
} 

La funzione isSet verifica se un bit è impostato e reverse esegue l'inversione dell'array sul posto. La subroutine di ordinamento sopra viene chiamato su ogni bit come segue (come nel radix sort):

sort(x) { 
    for (b = 1; b < 8; b++) { 
    sort(x, 0, n, b); 
    } 
} 

Il tempo totale di esecuzione è "O (7 * n log n)". Il fattore extra di 7 potrebbe essere variabile se questo algoritmo fosse generalizzato.

+0

Grazie, ma ne sono a conoscenza, come potete vedere dai miei commenti qui, e la vostra implementazione sembra ancora più lenta di quanto immaginassi :). Anche N * log (N) è piuttosto brutto in questo caso, poiché log2 (8M) ha 23. In realtà 7 * 23 * 8M è anche peggio di 128 * 8M necessari per estrarre i bit in ordine trovando tutte le chiavi corrispondenti. – Shelwien

+0

Oh, ok, pensavo che la tua unica lamentela fosse che non era una specie stabile. – jonderry

Problemi correlati