2012-01-03 18 views
7

Nel mio programma sto simulando un sistema N-body per un gran numero di iterazioni. Per ogni iterazione produco un insieme di coordinate 6N che devo aggiungere a un file e quindi utilizzare per eseguire l'iterazione successiva. Il codice è scritto in C++ e attualmente utilizza il metodo write() per scrivere i dati in formato binario ad ogni iterazione.Il modo più veloce per scrivere dati durante la produzione

Non sono un esperto in questo campo, ma mi piacerebbe migliorare questa parte del programma, poiché sono in procinto di ottimizzare l'intero codice. Sento che la latenza associata alla scrittura del risultato del calcolo ad ogni ciclo rallenta significativamente le prestazioni del software.

Sono confuso perché non ho esperienza di programmazione parallela e I/O di file di basso livello. Ho pensato di alcune tecniche astratte che ho immaginato avrei potuto realizzare, dal momento che sono la programmazione per la moderna (possibilmente multi-core) macchine con sistemi operativi Unix:

  • Scrittura dei dati nel file in blocchi di n iterazioni (sembra ci essere modi migliori per procedere ...)
  • parallelizzare il codice con OpenMP (come implementare effettivamente un buffer in modo che i fili sono sincronizzati in modo appropriato, e non si sovrappongono?)
  • Uso mmap (la dimensione del file potrebbe essere enorme, nell'ordine dei GB, questo approccio è abbastanza robusto?)

Tuttavia, non so come implementarli al meglio e combinarli in modo appropriato.

+0

Qual è la tua domanda? –

+6

"Sento che la latenza associata alla scrittura del risultato del calcolo in ogni ciclo rallenta significativamente le prestazioni del software." lo senti o hai profilato il tuo codice? –

+0

Hai profilato il codice per assicurarti che i tuoi sentimenti riguardo alla latenza siano corretti? – Grizzly

risposta

3

Naturalmente scrivere in un file ad ogni iterazione è inefficiente e molto probabilmente rallenterà il tuo computer. (come regola generale, dipende dal caso actuel)

È necessario utilizzare un modello di progettazione producer ->consumer. Saranno collegati da una coda, come un nastro trasportatore.

  • Il produttore cercherà di produrre il più velocemente possibile, rallentando solo se il consumatore non può gestirlo.
  • Il consumatore proverà a "consumare" il più velocemente possibile.

Dividendo i due, è possibile aumentare le prestazioni più facilmente perché ogni processo è più semplice e ha meno interferenze dall'altro.

  • Se il produttore è più veloce, è necessario migliorare il consumatore, nel tuo caso, scrivendo in un file in modo più efficiente, blocco per blocco molto probabilmente (come hai detto)
  • Se il consumatore è più veloce , è necessario migliorare il produttore, molto probabilmente parallelizzando come hai detto tu.

C'è non è necessario per ottimizzare entrambi. Ottimizza solo il più lento (il collo di bottiglia).

In pratica, si utilizzano thread e una coda sincronizzata tra di loro. Per suggerimenti sull'implementazione, date un'occhiata allo here, in particolare al § 18.12 "The Producer-Consumer Pattern".

Per quanto riguarda la gestione del flusso, è necessario aggiungere un po 'più di complessità selezionando una "dimensione massima della coda" e facendo attendere il produttore (i) se la coda non ha spazio sufficiente. Attenzione ai deadlock, quindi, codificalo attentamente. (vedi il collegamento wikipedia che ho dato a riguardo)

Nota: è consigliabile utilizzare i thread di boost perché i thread non sono molto portabili. (beh, lo sono dal C++ 0x ma la disponibilità di C++ 0x non è ancora buona)

+0

Ma, data la dimensione dei dati, come gestire il caso in cui il produttore è così veloce da produrre qualcosa che non può essere archiviato in memoria prima che l'utente riesca a salvarlo sul disco? –

+2

@Fiat Lux Beh, se produci dati più velocemente di quanto possa essere archiviato sul tuo filesystem, hai un problema piuttosto semplice, completamente indipendente dai trucchi del software che fai. Se la tua larghezza di banda è troppo piccola, prima o poi esaurirai lo spazio del buffer e poi dovrai gestire comunque quel caso. – Voo

+1

@FiatLux è per questo che ho detto "procducer ... rallentando solo se il consumatore non può gestirlo". Ho modificato per aggiungere un collegamento a un esempio di implementazione + miglioramenti suggeriti. Dovrai far aspettare i produttori se la memoria è piena. – Offirmo

1

È meglio suddividere l'operazione in due processi indipendenti: data-producing e file-writing. Data-producing userebbe un po 'di buffer per il passaggio dei dati in iterazione, e file-writing userebbe una coda per memorizzare le richieste di scrittura. Quindi, data-producing pubblicherebbe solo una richiesta di scrittura e proseguirà, mentre file-writing farebbe fronte alla scrittura in background.

In sostanza, se i dati vengono generati molto più rapidamente di quanto possa essere memorizzato, si finisce rapidamente a conservare la maggior parte nel buffer. In tal caso il tuo approccio attuale sembra abbastanza ragionevole, dal momento che si può fare poco a livello programmatico per migliorare la situazione.

+0

Perché pensi di poter fare un lavoro di buffering migliore di quello che già la cache dei blocchi del sistema operativo fa? –

+0

@SteveC Poiché è generalmente meglio implementare l'algoritmo in modo deterministico, affidarsi a funzioni dipendenti dall'implementazione. Voglio dire, la cache di blocco * è * buona, ma potrebbe o meno adattarsi alla situazione specifica. Potrebbe non essere così portabile, potrebbe non essere così veloce, ecc., E l'OP non era comunque troppo specifico :) – vines

+0

In questo caso non si dovrebbe passare attraverso due livelli di buffering, ma semplicemente bypassare il filesystem e scrivere a il disco grezzo. Questo è "più deterministico" e puoi "adattarlo alla situazione specifica". Personalmente, dubito che tu possa fare un lavoro migliore degli autori del filesystem. –

0

"Uso mmap (la dimensione del file potrebbe essere enorme, dell'ordine di GB, è questo approccio robusto abbastanza?)"

mmap è il metodo di caricamento dei programmi, librerie condivise e del sistema operativo il file page/swap: è robusto come qualsiasi altro I/O di file e generalmente offre prestazioni più elevate.

MA nella maggior parte dei sistemi operativi è difficile/difficile/impossibile espandere la dimensione di un file mappato mentre è in uso. Quindi, se conosci la dimensione dei dati, o stai solo leggendo, è fantastico. Per un log/dump che si aggiungono continuamente è meno affidabile, a meno che non si conoscano delle dimensioni massime.

+0

Conosco la dimensione dei dati, ma non ci saranno problemi con i sistemi a 32 bit se ho un file di qualche gigabyte? –

+0

@Fiat: questo è il grande vantaggio di mmap. È possibile mappare più viste nel file con offset in modo da poter utilizzare una finestra per accedere a qualsiasi parte di un file di dimensioni sufficienti. I dettagli dipendono dal tuo OS –

1

Se non vuoi giocare con roba in un thread diverso, puoi provare a usare aio_write(), che consente l'asincrono scrive. Fondamentalmente si fornisce al sistema operativo il buffer da scrivere, e la funzione ritorna immediatamente e termina la scrittura mentre il programma continua, è possibile controllare in seguito per vedere se la scrittura è stata completata.

Questa soluzione continua a soffrire del problema produttore/consumatore menzionato in altre risposte, se l'algoritmo sta producendo dati più velocemente di quanto possa essere scritto, alla fine si esaurirà la memoria per memorizzare i risultati tra l'algoritmo e la scrittura , quindi dovresti provarlo e vedere come funziona.

Problemi correlati