2010-09-07 12 views
13

Ho bisogno di scrivere un programma di ordinamento in C e sarebbe bello se il file potesse essere ordinato in posizione per risparmiare spazio su disco. I dati sono preziosi, quindi devo assicurarmi che se il processo viene interrotto (ctrl-c) il file non sia corrotto. Posso garantire che il cavo di alimentazione sulla macchina non venga strappato.Algoritmo di ordinamento interrompibile sul posto

Dettagli aggiuntivi: file è ~ 40GB, i record sono a 128 bit, la macchina è a 64 bit, OS è POSIX

Eventuali suggerimenti su realizzazione di questo, o le note in generale?

Grazie!

Per chiarire: Mi aspetto che l'utente desideri ctrl-c il processo. In questo caso, voglio uscire con garbo e garantire che i dati siano al sicuro. Quindi questa domanda riguarda la gestione degli interrupt e la scelta di un algoritmo di ordinamento che può essere riassunto rapidamente se richiesto.

Seguito (2 anni dopo): Solo per i posteri, ho installato il gestore SIGINT e ha funzionato benissimo. Questo non mi protegge da interruzioni di corrente, ma è un rischio che posso gestire. Codice a https://code.google.com/p/pawnsbfs/source/browse/trunk/hsort.c e https://code.google.com/p/pawnsbfs/source/browse/trunk/qsort.c

+0

Penso che devi chiedere Qual è più importante? Spazio o se il processo deve essere interrotto.Se hai bisogno di assicurarti che il file non sia corrotto, dovrai tenere traccia dei suoi progressi e degli stati precedenti in qualche modo - questo occuperà più spazio del file. –

+0

Per sicurezza. Fai una copia e ordina la copia. Non riesco a immaginare che il file system avrebbe problemi con un altro 40GB –

+0

@ Martin: la tua immaginazione non funziona come fa il mio. Sulla mia unità primaria attualmente ho 36,4 GB gratuiti. –

risposta

5

Installare un gestore per SIGINT che imposta appena un flag "processo dovrebbe uscire presto".

Nel tuo tipo, controlla la bandiera dopo ogni scambio di due record (o dopo ogni scambio N). Se la bandiera è impostata, salva.

+0

Questa sembra la soluzione migliore per me. Alcuni degli altri consigli mi danno l'impressione che Ctrl-C debba essere ignorato se non viene premuto al momento giusto, il che mi sembra una scarsa esperienza utente. –

+0

Sembra il vincitore. Sembra applicarsi a quicksort e heapsort. Grazie! –

+0

Tuttavia, non salverà i dati da kill -9 o un'interruzione di corrente. –

3

Utilizzare l'ordinamento heap e impedire interruzioni (ad esempio segnali di blocco) durante ogni operazione di scambio.

+0

Sì. Semplice e facile. Anche apportare modifiche in memoria e quindi scrivere/svuotare tutti in una volta (segnali di blocco per questa parte). –

+0

Heapsort richiede di colpire su tutto il file, il che potrebbe non essere buono per tutto ciò che non si adatta alla memoria fisica. –

+0

D'altra parte, se il file è gigantesco come nel tuo caso, il big-O e non il fattore costante dei miss della cache io sta per dominare. L'ordinamento heap è l'unico ordinamento sul posto migliore del quadratico grande-O. –

0

Eseguire il backup di qualsiasi cosa si prevede di modificare. Metti una bandiera che segna un successo. Se tutto è a posto, conservare il risultato, altrimenti ripristinare il backup.

+0

-1 non soddisfa il criterio sul posto della domanda, che ha chiaramente indicato che esiste un enorme set di dati e forse non c'è spazio per il backup. –

+0

R: Non è necessario salvare l'intero file. Basta salvare piccoli pezzi che vengono utilizzati in ordine. Non ho mai detto che l'intero file deve essere eseguito il backup. –

12

Jerry ha ragione, se è solo Ctrl-C sei preoccupato, puoi ignorare SIGINT per i periodi alla volta. Se vuoi essere una prova contro la morte del processo in generale, hai bisogno di una sorta di journalling minimale. Per scambiare due elementi:

1) Aggiungere un record a una struttura di controllo alla fine del file o in un file separato, indicando quali due elementi del file si intende scambiare, A e B.

2) Copia A nello spazio di lavoro, registra che lo hai fatto, a filo.

3) Copia B su A, quindi registrare nello spazio graffio che avete fatto così, a filo

4) Copia dallo spazio zero nel corso B.

5) Rimuovere il record.

Questo è O (1) spazio aggiuntivo per tutti gli scopi pratici, quindi conta ancora come posto nella maggior parte delle definizioni. In teoria, la registrazione di un indice è O (log n) se n può essere arbitrariamente grande: in realtà è un log n molto piccolo, e ragionevole hardware/tempo di esecuzione lo limita sopra a 64.

In tutti i casi quando dico " flush ", intendo che commetto i cambiamenti" abbastanza lontano ". A volte l'operazione di flush di base esegue solo lo svuotamento dei buffer all'interno del processo, ma in realtà non sincronizza il supporto fisico, perché non svuota i buffer completamente attraverso i livelli del driver/hardware del sistema operativo. Questo è sufficiente quando tutto ciò di cui sei preoccupato è la morte del processo, ma se sei preoccupato per l'improvviso smarrimento dei media, dovrai lavare il driver. Se fossi preoccupato per l'interruzione di corrente, dovresti sincronizzare l'hardware, ma non lo sei. Con un UPS o se pensi che le interruzioni di corrente siano così rari non ti dispiace perdere i dati, va bene.

All'avvio, controllare lo spazio zero per eventuali record "swap in progress".Se ne trovi uno, calcola quanto lontano hai e completa lo scambio da lì per riportare i dati in uno stato sonoro. Quindi inizia di nuovo il tuo tipo.

Ovviamente c'è un problema di prestazioni qui, dal momento che stai facendo il doppio dei record di scrittura di prima, e gli svuotamenti/sincronizzazioni possono essere sorprendentemente costosi. In pratica, il tuo ordinamento sul posto potrebbe avere delle operazioni di roba composta, che implicano molti scambi, ma che puoi ottimizzare per evitare che ogni elemento colpisca lo spazio di lavoro. Devi solo assicurarti che prima di sovrascrivere qualsiasi dato, ne hai una copia da qualche parte e un record di dove dovrebbe essere quella copia per riportare il tuo file a uno stato in cui contiene esattamente una copia di ciascun elemento.

Jerry ha anche ragione che il vero ordinamento sul posto è troppo difficile e lento per la maggior parte degli scopi pratici. Se è possibile risparmiare parte lineare della dimensione del file originale come spazio zero, si avrà un tempo molto migliore con un ordinamento di fusione.

Sulla base dei vostri chiarimenti, non avreste bisogno di alcuna operazione di scarico anche con un ordinamento sul posto. Hai bisogno di spazio di memoria in memoria che funzioni allo stesso modo e che il tuo gestore SIGINT possa accedere per rendere i dati protetti prima dello in uscita, piuttosto che ripristinare all'avvio dopo un'uscita anomala ed è necessario accedere a quella memoria in un modo sicuro dal punto di vista dei segnali (che tecnicamente significa usare uno sig_atomic_t per segnalare quali modifiche sono state apportate). Anche così, probabilmente stai meglio con un mergesort di un vero ordinamento sul posto.

+0

Penso di utilizzare un file mappato in memoria per lo spazio di lavoro. Dovrebbe darti il ​​meglio di entrambi i mondi. 1) Overhead basso per gli accessi perché è memorizzato nella cache e nessun calll API 2) Se il processo muore, il sistema operativo dovrebbe comunque scaricare la memoria modificata sul disco indipendentemente da come il processo muore. – torak

+0

@torak: ottime notizie, non sapevo che POSIX ha fornito tale garanzia per mmap. Ho ancora la preoccupazione che l'accesso al file possa essere riordinato, rendendolo inaffidabile per il ripristino. Quindi tutti gli indicatori potrebbero essere necessari per "volatile" o qualcosa del genere. –

+0

Dovresti usare 'msync()' con 'MS_SYNC' in ogni punto di flush. 'msync()' dovrebbe anche implicare una barriera per il compilatore, quindi 'volatile' non dovrebbe essere necessario. – caf

5

La parte per la protezione da ctrl-c è piuttosto semplice: signal(SIGINT, SIG_IGN);.

Per quanto riguarda l'ordinamento stesso, un ordinamento di unione generalmente funziona bene per l'ordinamento esterno. L'idea di base è quella di leggere quanti più dischi possibile nella memoria, ordinarli, quindi scriverli su disco. Di gran lunga il modo più semplice per gestirlo è scrivere ogni esecuzione su un file separato su disco. Quindi li unisci di nuovo insieme: leggi il primo record di ogni analisi in memoria e scrivi il file più piccolo nel file originale; leggere un altro record della corsa che ha fornito quel record e ripetere fino al completamento. L'ultima fase è l'unica volta che stai modificando il file originale, quindi è l'unica volta in cui hai davvero bisogno di assicurarti contro le interruzioni e così via.

Un'altra possibilità è utilizzare un ordinamento di selezione. Il punto negativo è che l'ordinamento stesso è piuttosto lento. Il punto buono è che è abbastanza facile scriverlo per sopravvivere quasi a tutto, senza usare molto spazio extra. L'idea generale è piuttosto semplice: trova il record più piccolo nel file e sostituiscilo nel primo punto. Quindi trova il record più piccolo di ciò che è rimasto, e scambialo nel secondo, e così via fino a quando non viene completato. Il punto buono è che il journaling è banale: prima di fare uno swap, si registrano i valori dei due record che si intendono scambiare. Poiché l'ordinamento viene eseguito dal primo all'ultimo all'ultimo, l'unica altra cosa da monitorare è quanti record sono già ordinati in un dato momento.

+1

+1: non si adatta alla domanda specifica, ma rappresenta l'approccio più appropriato. Non modifica il file master durante l'ordinamento dei blocchi e, se il processo di unione si interrompe, i file di blocchi ordinati vengono copiati come backup. – snemarch

+1

Invece di ignorare 'SIGINT', dovresti ** bloccare ** esso (e tutti gli altri segnali) durante le operazioni di scambio, ma periodicamente sbloccarlo in modo che i segnali in sospeso che arrivano mentre sono bloccati possano essere elaborati. –

-1

Supponendo un sistema operativo a 64 bit (si è detto che è una macchina a 64 bit ma potrebbe essere ancora operativo con sistema operativo a 32 bit), è possibile utilizzare mmap per mappare il file su un array, quindi utilizzare qsort sull'array.

Aggiungere un gestore per SIGINT per chiamare msync e munmap per consentire all'applicazione di rispondere a Ctrl-C senza perdere i dati.

+1

-1 per la risposta errata. Se un segnale interrompe 'qsort', i dati sono in uno stato indeterminato fino a quando' qsort' finisce. Non c'è assolutamente alcun modo di usare il 'qsort' del sistema per soddisfare le esigenze dell'OP. –

Problemi correlati