2010-08-09 33 views
16

Sto mantenendo un parser CSV ad alte prestazioni e cerco di sfruttare al massimo la tecnologia più recente per migliorare il throughput. Per questa particolare attività questo significa:Java: decodifica stream di carattere multithreading

  • di memoria flash (Possediamo una scheda relativamente poco costoso PCI-Express, prestazioni 1 TB di storage che raggiunge 1 GB/s sostenuta leggere)
  • più core (Possediamo un buon mercato Server Nehalem con 16 thread hardware)

La prima implementazione del parser CSV era a thread singolo. Lettura dei file, decodifica dei caratteri, divisione dei campi, analisi del testo, tutti all'interno della stessa discussione. Il risultato è stato un throughput di circa 50 MB/s. Non male ma ben al di sotto del limite di archiviazione ...

La seconda implementazione utilizza un thread per leggere il file (a livello di byte), un thread per decodificare i caratteri (da ByteBuffer a CharBuffer) e più thread da analizzare i campi (intendo l'analisi di campi di testo delimitati in doppi, interi, date ...). Funziona bene più velocemente, vicino a 400 MB/s sulla nostra scatola.

Ma ancora ben al di sotto delle prestazioni del nostro magazzino. E quelli SSD miglioreranno di nuovo in futuro, non stiamo sfruttando il massimo in Java. È chiaro che la limitazione corrente è la decodifica del personaggio (CharsetDecoder.read (...)). Questo è il collo di bottiglia, in un potente processore Nehalem trasforma i byte in caratteri a 400 MB/s, piuttosto buono, ma questo deve essere a thread singolo. Il CharsetDecoder è piuttosto statico, a seconda del set di caratteri utilizzato, e non supporta la decodifica multithread.

Quindi la mia domanda alla comunità è (e vi ringrazio per aver letto il post finora): qualcuno sa come parallelizzare l'operazione di decodifica del charset in Java?

risposta

2

qualcuno sa come parallelizzare l'operazione di decodifica del charset in Java?

Potrebbe essere possibile aprire più flussi di input per eseguire questa operazione (non sono sicuro di come procedere con NIO, ma deve essere possibile).

Quanto difficile dipenderà dalla codifica da cui si sta decodificando. Avrai bisogno di una soluzione su misura per la codifica del target. Se la codifica ha una larghezza fissa (ad esempio Windows-1252), quindi un byte == un carattere e la decodifica è facile.

Le moderne codifiche a larghezza variabile (come UTF-8 e UTF-16) contengono regole per identificare il primo byte di una sequenza di caratteri, quindi è possibile passare al centro di un file e avviare la decodifica (si prendere nota della fine del blocco precedente, quindi è consigliabile iniziare prima a decodificare la fine del file).

Alcune codifiche legacy di larghezza variabile potrebbero non essere così ben progettate, quindi non avrete altra scelta che decodificare dall'inizio dei dati e leggerli in sequenza.

Se è un'opzione, generare i dati come UTF-16BE. Quindi puoi tagliare la decodifica e leggere due byte direttamente in un carattere.

Se il file è Unicode, fare attenzione alla gestione delle BOM, ma suppongo che tu abbia già familiarità con molti dei dettagli di basso livello.

+0

Sfortunatamente, UTF-16 è una codifica a lunghezza variabile. È necessario UTF-32 per l'analisi semplice di Unicode. – grddev

+0

@grddev - Ho trattato questo nel mio post - è possibile identificare sequenze di caratteri nel mezzo di flussi di dati UTF-16 - alte coppie di surrogati sono 0xD800-0xDBFF e surrogati bassi sono 0xDC00-0xDFFF. Qualsiasi altra cosa è contenuta in coppia di byte. – McDowell

+0

Il mio commento si riferisce alla menzione di UTF-16BE. Non è possibile ritagliare completamente la decodifica. Ma è davvero molto semplice. – grddev

1

È chiaro che la limitazione corrente è la decodifica del carattere (CharsetDecoder.read (...))

Come lo sai? Il tuo monitoraggio/profilo mostra in modo definitivo che il thread del decoder utilizza il 100% di uno dei tuoi core?

Un'altra possibilità è che il sistema operativo non è in grado di guidare l'SSD alla velocità massima teorica.

Se la decodifica UTF-8 è sicuramente il collo di bottiglia, allora dovrebbe essere possibile eseguire l'attività in parallelo. Ma sicuramente dovrai implementare i tuoi decoder per farlo.

+0

Sì, diverse esecuzioni con JProfiler mostrano chiaramente che il thread di decodifica dei caratteri (singolo) è attivo quasi il 100% delle volte. Vedo più riferimenti alla codifica UTF-8 e UTF-16 nelle risposte. Ma stiamo scrivendo qui un parser CSV generico, che sarà usato su file esistenti, dai nostri clienti in Europa, Stati Uniti, Giappone, Cina ... Quindi non possiamo assumere quale charset verrà usato. In particolare, non possiamo assumere che il set di caratteri sia a lunghezza fissa o meno. – Killerchamb

0

Se si conosce la codifica ed è di dimensioni fisse o non contiene sequenze di byte sovrapposte, è possibile eseguire la scansione di una sequenza speciale. In CSV, una sequenza per newline potrebbe avere senso. Anche se si rileva dinamicamente la codifica, è possibile eseguire un passaggio dei primi pochi byte per determinare la codifica e quindi passare alla decodifica parallela.

+0

Mi piace molto questa idea. Individuazione dei delimitatori direttamente all'interno dei byte non elaborati. E sì, il pattern NEW_LINE è il candidato giusto per un parser CSV. Ma devo supportare qualsiasi set di caratteri.Sei a conoscenza di qualche metodo generico sull'implementazione di charset che indica se i pattern di byte si sovrappongono o no? Non vedo nessuno nel Javadoc. – Killerchamb

+0

@Antoine: Purtroppo, non ne ho idea. Non dovrebbe essere un problema in nessuna delle codifiche UTF o in una codifica a larghezza fissa in generale. [Secondo questa domanda] (http://stackoverflow.com/questions/724247/newline-control-characters-in-multi-byte-character-sets) non dovrebbero esserci problemi per le tipiche codifiche giapponesi. Non so se alcune delle rappresentazioni di nuova riga si sovrappongano alle codifiche cinesi (o di altro tipo). E 'in ogni caso chiaro che le interfacce esistenti in Java non forniscono i mezzi per farlo bene. :( – grddev

0

Un'altra alternativa (pazzesca) sarebbe semplicemente separare l'input in blocchi di dimensioni arbitrarie, ignorare i problemi di decodifica e quindi decodificare ciascuno dei blocchi in parallelo. Tuttavia, si desidera assicurarsi che i blocchi si sovrappongano (con una dimensione parametrizzata). Se la regione di sovrapposizione dei due blocchi viene decodificata allo stesso modo dai due thread (e la sovrapposizione è abbastanza grande per la codifica specificata), dovrebbe essere sicuro di unire i risultati. Maggiore è la sovrapposizione, maggiore è l'elaborazione richiesta e minore è la probabilità di errore. Inoltre, se ti trovi in ​​una situazione in cui sai che la codifica è UTF-8 o una codifica simile in modo simile, puoi impostare la sovrapposizione piuttosto bassa (per quel client) e garantire comunque il corretto funzionamento.

Se il secondo blocco risulta errato, sarà necessario ripristinarlo, quindi è importante non eseguire grandi blocchi in parallelo. Se si eseguono più di due blocchi in parallelo, sarebbe importante "riparare" dall'inizio alla fine, in modo che un blocco disallineato non provochi l'invalidazione del blocco successivo (che potrebbe essere allineato correttamente).