2009-06-21 10 views
5

Heredé un fragmento de código que hace un uso intensivo de las conversiones String -> byte [] y viceversa para algún código de serialización nacional. Esencialmente, los objetos de Java saben cómo convertir sus partes constituyentes en cadenas que luego se convierten en un byte []. Dicha matriz de bytes se pasa luego a través de JNI al código de C++ que reconstituye el byte [] en C++ std :: cadenas y los utiliza para arrancar objetos de C++ que reflejan los objetos de Java. Hay un poco más, pero esta es una vista de alto nivel de cómo funciona este código; La comunicación funciona así en ambas direcciones, de modo que la transición C++ -> Java es una imagen especular de la transición Java -> C++ que mencioné anteriormente.¿Alguna sugerencia sobre cómo mejorar el rendimiento de una conversión de Java String a byte []?

Una parte de este código, la conversión real de una cadena en un byte [], se muestra inesperadamente en el generador de perfiles como la quema de una gran cantidad de CPU. De acuerdo, hay una gran cantidad de datos que se transfieren, pero esto es un cuello de botella inesperado.

El esquema básico del código es el siguiente:

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

No es un poco más a la función pero no mucho. La función anterior se llama una vez por cada objeto String/Stringified y después de que todos los componentes se escriben en ByteArrayOutputStream, ByteArrayOutputStream se convierte en un byte []. Romper lo anterior en una versión más amigable con el perfil extrayendo la llamada convert_me.getBytes() muestra que más del 90% del tiempo en esta función se gasta en la llamada getBytes().

¿Hay alguna forma de mejorar el rendimiento de la llamada a getBytes() o existe otra forma, potencialmente más rápida, de lograr la misma conversión?

El número de objetos que se están convirtiendo es bastante grande. En las ejecuciones de perfiles que utilizan solo un pequeño subconjunto de los datos de producción, veo algo así como más de 10 millones de llamadas a la función de conversión anterior.

Debido al hecho de que estamos muy cerca de liberar el proyecto en la producción, hay algunas soluciones que no son posibles en este punto en el tiempo:

  • reescribir la interfaz de serialización para pasar solo Cadena de objetos a través de la capa JNI. Esta es la manera obvia (para mí) de mejorar la situación, pero requeriría una reingeniería importante de la capa de serialización. Dado el hecho de que entraremos en la UAT a principios de esta semana, es demasiado tarde para hacer este tipo de cambio complejo. Es mi mejor tarea para el próximo lanzamiento, así que estará listo; Sin embargo, necesito una solución hasta entonces, pero hasta ahora el código está funcionando, se ha utilizado durante años y tiene la mayoría de las fallas. Bueno, aparte de la actuación.
  • Cambiar la JVM (actualmente 1.5) tampoco es una opción. Desafortunadamente esta es la JVM predeterminada que está instalada en las máquinas del cliente y desafortunadamente no es posible actualizar a 1.6 (que podría ser más rápido en este caso). Cualquiera que haya trabajado en grandes organizaciones probablemente entienda por qué ...
  • Además de esto, ya nos estamos topando con limitaciones de memoria, por lo que intentamos almacenar en caché al menos las cadenas más grandes y su representación de matriz de bytes, a la vez que una solución potencialmente elegante , es probable que cause más problemas de los que resolverá
+0

Hola Timo, sólo una pregunta tonta es la herramienta que perfilador ha utilizado? –

+0

@Castanho - Utilicé IBM Rational's PurifyPlus. –

Respuesta

4

Supongo que parte del problema puede ser que una cadena de Java está en formato UTF-16, es decir, dos bytes por carácter; así que getBytes() está haciendo un montón de trabajo para convertir cada elemento UTF-16 en uno o dos bytes, dependiendo de su conjunto de caracteres actual.

¿Ha intentado utilizar CharsetEncoder? Esto debería darle más control sobre la codificación de cadena y le permitirá omitir parte de la sobrecarga en la implementación predeterminada getBytes.

Como alternativa, ¿ha intentado especificar explícitamente el juego de caracteres en getBytes y usa US-ASCII como juego de caracteres?

+1

El OP no especifica un conjunto de caracteres para la llamada a getBytes() que, además de predeterminar a la configuración regional predeterminada actual, hace un montón de trabajo adicional para recuperar esa configuración regional. –

+0

Especificar la configuración regional en la llamada a getBytes() parece tener un efecto benigno en el consumo de memoria al menos, pero desafortunadamente no condujo a una mejora real en el tiempo de ejecución. El siguiente paso sería reescribir las funciones usando CharsetEncoder y ver si eso mejora las cosas. –

1

Si son las mismas cadenas que convierte todas las veces, puede almacenar en caché el resultado en un WeakHashMap.

Además, eche un vistazo al método getBytes() (la fuente está disponible si instala el SDK) para ver exactamente qué hace.

+0

El almacenamiento en caché parece una buena idea, pero desafortunadamente la función de conversión se llama millones de veces incluso con un conjunto de datos comparativamente pequeño y las cadenas son distintas. Es probable que exista cierta duplicación, pero dado que ya nos encontramos con limitaciones de memoria en las JVM de 32 bits, el almacenamiento en caché de las cadenas convertidas probablemente causará más problemas de los que resolverá. –

+0

Luego debe averiguar POR QUÉ la conversión es lenta ... –

+0

Parece estar relacionado con dos cosas: (a) la cantidad de datos (no se puede hacer mucho al respecto) y (b) el juego de caracteres/configuración regional Me estoy convirtiendo en. Hasta ahora, parece que la conversión a UTF-8 es considerablemente más rápida, lo que no es sorprendente, pero desafortunadamente el lado de C++ actualmente no es compatible con UTF-8. –

2

Veo varias opciones:

  • Si tiene Latin-1 cuerdas, usted podría dividir el byte más alto de los caracteres en la cadena (charset hace esto también creo)
  • También podría dividir el trabajo entre múltiples núcleos si tiene más (el marco fork-join tenía backport a 1.5 una vez)
  • También podría compilar los datos en un generador de cadenas y solo convertirlos en una matriz de bytes una vez al final.
  • Mire el uso de su GC/memoria. El exceso de utilización de la memoria podría retrasar sus algoritmos abajo debido interrupciones frecuentes GC
  • Cuestiones relacionadas