Qualche suggerimento su come migliorare le prestazioni di una conversione da stringa Java a byte []?

StackOverflow https://stackoverflow.com/questions/1023701

Domanda

Ho ereditato un pezzo di codice che fa un uso intensivo di String - > conversioni byte [] e viceversa per alcuni codici di serializzazione fatti in casa. Essenzialmente gli oggetti Java sanno come convertire le loro parti costituenti in stringhe che vengono poi convertite in byte []. Detto array di byte viene quindi passato attraverso JNI nel codice C ++ che ricostituisce il byte [] in C ++ std :: stringhe e li utilizza per avviare gli oggetti C ++ che rispecchiano gli oggetti Java. C'è un po 'di più, ma questa è una visione di alto livello di come funziona questo pezzo di codice; La comunicazione funziona in questo modo in entrambe le direzioni in modo tale che C ++ - > La transizione Java è un'immagine speculare di Java - > Transizione C ++ che ho menzionato sopra.

Una parte di questo codice - l'effettiva conversione di una stringa in un byte [] - viene inaspettatamente mostrata nel profiler mentre brucia molta CPU. Certo, ci sono molti dati che vengono trasferiti ma questo è un collo di bottiglia inaspettato.

La struttura di base del codice è la seguente:

public void convertToByteArray(String convert_me, ByteArrayOutputStream stream)
{
  stream.write(convert_me.getBytes());
}

C'è un po 'di più nella funzione ma non molto. La funzione sopra viene chiamata una volta per ogni oggetto String / Stringified e dopo che tutti i componenti sono stati scritti in ByteArrayOutputStream, ByteArrayOutputStream viene convertito in un byte []. Scomponendo quanto sopra in una versione più adatta al profiler estraendo la chiamata convert_me.getBytes () mostra che oltre il 90% del tempo in questa funzione viene impiegato nella chiamata getBytes ().

Esiste un modo per migliorare le prestazioni della chiamata getBytes () o esiste un altro modo potenzialmente più veloce per ottenere la stessa conversione?

Il numero di oggetti che vengono convertiti è piuttosto grande. Nelle sequenze di profilatura che utilizzano solo un piccolo sottoinsieme dei dati di produzione, vedo qualcosa come 10 milioni e più chiamate alla funzione di conversione sopra.

A causa del fatto che siamo molto vicini al rilascio del progetto in produzione, ci sono alcune soluzioni alternative che al momento non sono possibili:

  • Riscrivi l'interfaccia di serializzazione per passare semplicemente gli oggetti String attraverso il livello JNI. Questo è il modo ovvio (per me) di migliorare la situazione, ma richiederebbe un reingegnerizzazione importante del livello di serializzazione. Dato che entreremo in UAT all'inizio di questa settimana, è troppo tardi per apportare questo tipo di cambiamento complesso. È il mio top todo per la prossima versione, quindi sarà fatto; Ho comunque bisogno di una soluzione alternativa fino ad allora, ma finora il codice funziona, è stato usato per anni e ha risolto la maggior parte dei nodi. Bene, a parte la performance.
  • Anche la modifica della JVM (attualmente 1.5) non è un'opzione. Sfortunatamente questa è la JVM predefinita installata sui computer client e l'aggiornamento a 1.6 (che potrebbe o non potrebbe essere più veloce in questo caso) non è purtroppo possibile. Chiunque abbia lavorato in grandi organizzazioni probabilmente capisce perché ...
  • Oltre a questo, stiamo già incontrando vincoli di memoria, quindi tentando di memorizzare nella cache almeno le stringhe più grandi e la loro rappresentazione di array di byte, pur essendo una soluzione potenzialmente elegante, è probabile che causi più problemi di quanti ne risolva
È stato utile?

Soluzione

Suppongo che parte del problema potrebbe essere che una stringa Java è in formato UTF-16, ovvero due byte per carattere; quindi getBytes () sta facendo un sacco di lavoro per convertire ogni elemento UTF-16 in uno o due byte, a seconda del set di caratteri corrente.

Hai provato a utilizzare CharsetEncoder - questo dovrebbe darti un maggiore controllo sulla codifica String e consentire di saltare parte dell'overhead nell'implementazione predefinita getBytes .

In alternativa, hai provato a specificare esplicitamente il set di caratteri su getBytes e hai utilizzato US-ASCII come set di caratteri?

Altri suggerimenti

Vedo diverse opzioni:

  • Se hai stringhe Latin-1, potresti semplicemente dividere il byte più alto dei caratteri nella stringa (anche Charset fa questo, credo)
  • Puoi anche dividere il lavoro tra più core se ne hai di più (il framework fork-join aveva il backport a 1.5 una volta)
  • Potresti anche costruire i dati in un stringbuilder e convertirli in array di byte solo una volta alla fine.
  • Guarda il tuo GC / utilizzo della memoria. L'uso eccessivo della memoria potrebbe rallentare gli algoritmi a causa di frequenti interruzioni del GC
  • Se si tratta sempre delle stesse stringhe che si convertono, è possibile memorizzare nella cache il risultato in una WeakHashMap.

    Inoltre, dai un'occhiata al metodo getBytes () (il sorgente è disponibile se installi l'SDK) per vedere esattamente cosa fa.

    Il problema è che tutti i metodi in Java, anche oggi, allocano la memoria con la produzione UTF-8. Per ottenere il performante di codifica dovrai scrivere codice personalizzato e riutilizzare il buffer byte []. Colfer può generare il codice o semplicemente copiarne l'implementazione.

    scroll top