jueves, 30 de octubre de 2008

Fast InfoSet, Fast Web Services & XML encoding rules (XER) for ASN.1


[Aviso para navegantes:

A diferencia del resto de temas, en los que hablo desde el conocimiento o experiencia (o al menos desde un cierto conocimiento, tampoco vamos a ponernos chulos), sobre este tema en concreto no tengo experiencia ni he podido hacer pruebas. Se trata de una línea de investigación que abrí hace algún tiempo y que no he tenido tiempo de cerrar u ocasión/excusa por concurrencia importante en accesos a Web Services de mi proyecto... ]


Curioso tema. Pongámosmos bíblicos...

Al principio era el COBOL. Las estructuras eran sencillas, fáciles y rápidas de parsear, los sistemas intercambiaban ficheros con campos de anchura fija. Y vio que era bueno...

Luego llegó el C y el C++, y empezó a requerirse optimización en los tamaños de los ficheros así como en el parseo. Y llegaron los mensajes con campos de tamaño variable en los que al comienzo de cada campo se indica la longitud del mismo. Existieron múltiples implementaciones, quizás la más estandarizada sea el ASN.1 (Abstract Syntax Notation One). Tremendamente rápidos de parsear y eficientes en espacio excepto si el contenido de los campos es de tamaño pequeño en media.

Luego llegó la era de Internet. De la interoperabilidad y de lo que mola. Y alguien inventó el XML que son mensajes estructurados donde los campos tienen delimitadores de inicio y de fin para ser parseados por aplicaciones pero legibles para el humano en base al éxito del HTML. Muy mono, muy manejable, muy entendible, pero muy ineficiente tanto en tamaño como en velocidad de parseo.
Vamos, que como el cangrejo. Vamos hacia atrás. No se "nota mucho" porque en paralelo se ha incrementado la potencia de procesamiento y la velocidad de las redes. Pero, cuando hay intercambio exhaustivo de mensajería o saturación del ancho de banda, la solución "elegante" puede no servir.

En este enlace se explica el problema de de forma más seria, pero en el fondo dicen lo mismo que yo... http://java.sun.com/developer/technicalArticles/WebServices/fastWS/:

(...)

XML-based messaging is at the heart of the current Web Services technology. XML's self-describing nature has significant advantages, but they come at the price of bandwidth and performance.

XML-based messages are larger and require more processing than existing protocols such as RMI, RMI/IIOP or CORBA/IIOP: data is represented inefficiently, and binding requires more computation. For example, an RMI service can perform an order of magnitude faster than an equivalent Web Service. Use of HTTP as the transport for Web Services messages is not a significant factor when compared to the binding of XML to programmatic objects.

Increased bandwidth usage affects both wired and wireless networks. Often the latter, e.g. mobile telephone networks, have bandwidth restrictions allotted for communication by a network device. In addition, larger messages increase the possibility of retransmission since the smaller the message, the less likely it will be corrupted when in the air.

Increased processing requirements affects network devices communicating using both types of networks (wired and wireless). A server may not be able to handle the throughput the 'network' demands of it. Mobile phone battery life may be reduced as a device uses more memory, performs more processing and spends more time transmitting information. As the scale of Web Services usage increases, these problems are likely to be exacerbated.
Y en esta línea, hace ya algunos años, se comenzaron dos iniciativas, lideró unas propuestas y especifcaciones llamadas "Fast Infoset" (trabajo conjunto entre ISO/IEC JTC 1 y ITU-T) y "Fast Web Services" (iniciativa de Sun Microsystems y también en proceso de estandarización por ambas organizaciones).

Muy resumidamente y en términos mundanos significa que los mensajes y estructuras se serializan codificados por debajo en estructuras binarias ASN.1, pero que el las partes (enviante y receptor) se tratar como si fuera XML normal y corriente. Es decir, en términos de capas OSI, los niveles de transporte y protocolo cambian, pero no cambia el nivel de aplicación.

Para los que quieran más detalles, sus números de estándar son:


  • ITU-T Rec. X.891 ISO/IEC 24824-1
  • ITU-T Rec. X.892 ISO/IEC 24824-2
Y, además de los enlaces que he incluido arriba (de Sun Microsystems), estas presentaciones son muy legibles:
Y estos dos enlaces también están muy bien escritos:
Y en este blog hay toda una catogría muy interesante al respecto...
Desde mi punto de vista el tema en si mismo es para partirse de la risa (¿por qué hemos usado ASN.1 desde el principio y nos dejamos de tontadas?) y aparte, creo que esto soluciona sólo parte del problema (el transporte, aunque si usamos compresión GZIP ya ganamos bastante) pero no tiempo de computación y tratamiento. Amén de que dos no pelean si uno no quiere, es decir, ambos partners en el diálogo tienen que ser capaces de generar y entender estos Fast Web Services; si no, estamos jodidos...

Bueno, y ahora las implementaciones... Pues la buena noticia es que todo esto no es sólo teoría o desarrollos de difícil integración, sino que Sun tiene una implementación Open Source dentro del paraguas de Glassfish:

Si tengo tiempo de probarlo, os aviso...

Pero no sólo de Sun vive el hombre... Al menos están estas tres implementaciones conocidas:


Raro, ¿verdad?

jueves, 23 de octubre de 2008

Tuning de NetBeans 7.x y 6.x

[Actualizado el 27/4/2011].

Otro tip de rendimiento que no tiene que ver con el entorno de explotación, sino con el entorno de desarrollo. Mejoras de rendimiento de Netbeans 6/7 a través de opciones de arranque y otros factores...

¿Habéis probado a cambiar parámetros de lanzamiento del Netbeans? Pueden verse algunos tips recomendados por el propio equipo de Netbeans en:
Yo utilizo Java 6 update 25 y tocando algunas opciones de lanzamiento "interesantes" me va bastante-bastante bien en mi portátil con Centrino Duo. Aunque en realidad, mucho más importante que todo esto, lo que más me ha ayudado a aligerar el entorno es cambiar de disco duro a un híbrido con 4 GB de SDD y revolución a 7200 rmp (Seagate Momentus XT) así como desactivar todos los módulos y plugins que no se utilizan ni se van a utilizar, y tener cuidado de excluir las carpetas de proyectos de los scans antivirus.

El tema del escaneo constante de cambios en proyectos sigue siendo una pequeña pesadilla, pero estoy mitigándolo con las recomendaciones que en los enlaces anteriores se indican.

Mi fichero netbeans.conf contiene las siguientes opciones de lanzamiento:
###[serverperformance.blogspot.com]
netbeans_default_options="-J-Dcom.sun.aas.installRoot=\"C:\Java\glassfish-v2ur2\" -J-client -J-Xss2m -J-Xms32m -J-XX:PermSize=32m -J-XX:MaxPermSize=500m -J-XX:+UseConcMarkSweepGC -J-XX:+CMSClassUnloadingEnabled -J-XX:+CMSPermGenSweepingEnabled -J-Djava.index.useMemCache=true -J-Xverify:none -J-XX:+UseBiasedLocking -J-XX:+AggressiveOpts -J-Djava.net.preferIPv4Stack=true -J-Dsun.java2d.d3d=true -J-Dsun.java2d.noddraw=false -J-Dapple.laf.useScreenMenuBar=true -J-Dapple.awt.graphics.UseQuartz=true "

martes, 21 de octubre de 2008

Rendimiento de la instalación de Java CAPS

[Java CAPS 5.1.2 / Java CAPS 5.1.3]

Je, je, este no es un tip de rendimiento en tiempo de ejecución... sino un tip de rendimiento en tiempo de desarrollo, más bien en tiempo de instalación del repositorio y entorno de desarrollo...

De la documentación oficial de Sun:

3. Some optional but recommended performance enhancements:
  • Disable on-demand virus scanning on your Java CAPS installation directory. The large number of small files manipulated within the environment causes much unnecessary disk and CPU load if subject to constant scanning.
  • Set Internet Explorer’s cache size to 64Kb to improve the upload performance when uploading SAR files to the Repository. This is described in Microsoft’s Knowledge Base Article #329781


Aún con todo sigue siendo un infierno...

martes, 14 de octubre de 2008

Tuning de motor de JSP en Glassfish: Configuración para entorno de producción

Como continuación del post Glassfish V2: Performance Tuning, Tips & Tricks, a continuación pego algunos parámetros óptimos para entornos productivos en Glassfish V2. Recordad que Glassfish viene configurado por defecto "para desarrollo" y que pueden hacerse unos cuantos tunings para producción.

En el mencionado post están todos, amén de los parámetros generales de lanzamiento de las JVM que ya recomendé en el post JVM tuning: Parámetros de lanzamiento de JVM: Sun Hotspot.

Esta vez toca la configuración de nuestra aplicación web, en lo que concierne al motor de JSP. Los parámetros óptimos en mi experiencia y necesidades son:

[fichero sun-web.xml]:

(...)
<jsp-config>

<property name="development" value="false">
<description>If set to true, enables development mode, which allows JSP files to be checked for modification. Specify the frequency at which JSPs are checked using the modificationTestInterval property.</description>
</property>

<property name="usePrecompiled" value="true">
<description>
If set to true, an accessed JSP ile is not compiled. Its precompiled servlet class is used instead. It is assumed that JSP files have been precompiled, and their corresponding servlet classes have been bundled in the web application’s WEB-INF/lib or WEB-INF/classes directory.</description>
¡¡¡OJO!!!! Si se pone a true, hay que compilarlos a mano con jspc y dejarlos en el directorio indicado. En nuestro caso, lo ponemos a false pero al desplegar la aplicación en el servidor de aplicaciones, marcamos el check "Precompile JSP" en la consola, para que se haga en ese momento...
</description>
</property>

<property name="mappedfile" value="false">
<description>If set to true, generates static content with one print statement per input line, to ease debugging.</description>
</property>

<property name="suppressSmap" value="true">
<description>If set to true, generation of SMAP information for JSR 45 debugging is suppressed.</description>
</property>

<property name="fork" value="false">
<description>Specifies that Ant forks the compiling of JSP files, using a JVM machine separate from the one in which Tomcat is running.</description>
</property>

<property name="classdebuginfo" value="false">
<description>Specifies whether the generated Java servlets are compiled with the debug option set (-g for javac).</description>
</property>

<property name="xpoweredBy" value="false">
<description>If set to true, the X-Powered-By response header is added by the generated servlet. Buena idea para ocultar el AS por motivos de seguridad</description>
</property>

<property name="keepgenerated" value="true">
<description>If set to true, keeps the generated Java files. If false, deletes the Java files.</description>
</property>

</jsp-config>
(...)

Ni que decir tiene que estos los fuerzo así porque sus valores por defecto son los contrarios. Para más información no seas vago y abre los PDF de la documentación...

Nota: me extrañaría mucho que estas mismas opciones no funcionaran "tal cual" en Tomcat 5.5 y superiores... Por cierto que todavía no me queda claro si GF tiene un Tomcat dentro, si está inspirado en Tomcat, o si simplemente han respetado las propiedades de configuración para eliminar resistencias al cambio... :-)

martes, 7 de octubre de 2008

Cuidado con XA y JMS (o "Estoy hasta los huevos desde hace años")

Pues lo de casi siempre:

...mucha capa de abstracción, mucha transparecia para el desarrollador y mucha magia... y a la hora de la verdad hay que remangarse y entenderlo todo.

Porque en Java CAPS las colaboraciones que interactúan dentro de un conectivity map en el fondo son MDBs. Y los MDBs en el fondo son EJB disparados por colas JMS. Y por defecto las transacciones JMS son XA, con dos cojones.

Es decir: si tengo unas cuantas colaboraciones (en Java CAPS, léase "unos cuantos EJBs" para una visión más generalista del mismo problema) que interactúan "desacopladamente" entre si (¡qué gran palabra! casi a la altura de "sinergia" y de "talento"...), o un master que va lanzando colaboraciones/MDBs de forma asíncrona o síncrona (request/reply), parece lógico pensar que todos los recursos a los que se accedan estén en modo XA... al menos, si no pensamos, los declaramos así :-)

Nota: el objetivo de este artículo no es explicar el patrón request/reply, pero ahí dejo un diagrama ilustrativo (enlaza con la explicación del patrón en docs.sun.com):
Ya primera gran mentira viene ahí, porque en el 99% de los casos no sólo accedemos a bases de datos sino que también escribimos en logs o en ficheros a disco, o interactuamos con un monitor transaccional o hacemos una petición http o TCP cruda... Llevo más de 6 años sin ver dónde está la magia del XA y viendo sólo dolores de cabeza, pero todo esto es otra cuestión...

...Lo que viene al caso para este artículo es que si en una colaboración Java CAPS (o en un MDB normal si funcionamos fuera de un integrador) invocamos a base de datos con transacciones XA y la invocación JMS a ese componente estaba también en modo XA, se produce un deadlock: como la inserción/modificación falla, el sistema decide deshacer también el envío a la cola JMS y por supuesto no llega nunca a enviar el mensaje de respuesta a la colaboración maestra indicándole el fallo (para que desde ahí se reaccione o lo que sea); en lugar de eso, la invocación al segundo componente vuelve a dispararse porque la cola estaba en modo XA. Y vuelve a fallar la inserción. Y así indefinidamente.

Esto que he explicado fatal está perfectamente explicado en el siguiente artículo: http://blogs.sun.com/fkieviet/entry/request_reply_from_an_ejb.

La solución: en modelos síncronos (request/reply) nunca usar modo XA para el envío del mensaje de request, sino ponerlo a modo TRANSACTED. Y ya que estamos, si sólo interactuamos con una instancia de base de datos, el recurso JNDI tampoco lo definamos en modo XA sino normal. Manténgase en modo XA sólo las invocaciones asíncronas de verdad y si queremos evitar posibles duplicados en inserciones. Pero mi apuesta es por no usar XA para nada y gestioanr las cosas a mano: porque teniendo el control, tendremos toda la capacidad de reacción con incidencias en el entorno productivo en lugar de liarnos a intentar modelizar mentalmente qué narices está pasando.

Porque además, en nuestro caso, hemos ganado un 50% de rendimiento en algunos pasos y el servidor de aplicaciones va mucho más ligero y con menos sobrecarga de recursos y cosas raras... (¿no iba este blog sobre rendimiento?).

Como digo, véase el artículo referenciado (no habla de Java CAPS en particular sino de EJB y JMS en general): http://blogs.sun.com/fkieviet/entry/request_reply_from_an_ejb.

Ciao ciao

miércoles, 1 de octubre de 2008

Propuestas de optimizaciones para Strings

Al hilo de lo comentado en los posts Copy-on-write optimization for StringBuilder and StringBuffer y Copy-on-write optimization for StringBuilder and StringBuffer (II), he estado trabajando más de la mitad del mes de septiembre (robándole tiempo a mi mujer, al sueño y a la tele por se orden) en una idea que yo pensaba que era cojonuda, aunque aviso de antemano que probablemente no sea buena, vistos los comentarios y estadísticas recogidas por mi... :-(

Por cierto, antes de seguir leyendo, si no estás familiarizado con cómo están implementados los StringBuilders y StringBuffer en el JDK, échale un vistazo a esos fuentes y al Java Language Specifications, porque si no esto pierde todo su encanto. En general es una buena práctica revisar cómo la gente del JDK ha hecho ciertas cosas, yo he aprendido de ellos mucho-mucho en los últimos 11 años... al menos demuestra algo de interés por el tema :-)

La nueva idea básicamente consiste en intentar ver la creación de Strings desde una perspectiva nueva: si el 95% de las operaciones sobre un StringBuilder son appends (y no inserts, deletes, etc), quizás eso nos puede dar una pista de reimplementación basada en almacenar la lista de cadenas que se quieren concatenar y sólo hacer la concatenación efectiva al invocar al método toString().

Desde el punto de vista teórico supone una ventaja importante frente al modelo actual de StringBuilder ya que no es necesario crear un array de chars interno, una vez llenado crear otro más grande y copiar los contenidos de uno a otro, y finalmente volver a hacer una creación de array de chars con el tamaño exacto y copia de los caracteres allí, en el toString().

Después de muchas vueltas y muchos intentos y benchmarkings, mi propuesta pasaba por crear una clase java.lang.StringAppender, que contiene internamente una lista de Strings...

El principal problema es con la inserción de un sólo carácter, operación que es tremendamente frecuente a la vista de las pruebas. Mi solución es entre un 5 y un 160% mejor al concatenar Strings, pero más de un 100% peor al concatenar chars individualmente. Como digo he probado otras combinaciones (almacenar char[][], almacenar dos listas, una para Strings y otra para chars, modificar StringBuilder para que pueda funcionar en "fast mode" y "normal mode" según interese... pero la opción más limpia y de mejor rendimiento en los casos que son típicos en un desarrollador de aplicaciones web (y que yo suponía que esto era extrapolable a cualquier otro desarrollo, incluyendo el del JDK, Netbeans, etc), es la que adjunto.

Pero no es suficiente, o al menos sería difícil de justificar el cambio de API (que es algo que no gusta mucho, evidentemente)...

En el archivo de la lista de correo de desarrollo de las librerías de núcleo para JDK 7 puede verse el detalle completo, las distintas respuestas públicas (también hubo unos cuantos consejos "privados"), y descargar el código fuente tanto en los cambios a String.java propuestos como la nueva clase StringAppender.java (nota: si hay interés en descargarlo, tómese la última versión publicada, no la primera)... así que no me enrollaré mucho más.

Ahí dejo el enlace al thread: http://mail.openjdk.java.net/pipermail/core-libs-dev/2008-September/000734.html. Y esta es la copia literal de mi primer y mi tercer mensaje que son un resumen ejecutivo de la idea, las estadísticas y las conclusiones.

El primer mensaje a la lista (http://mail.openjdk.java.net/pipermail/core-libs-dev/2008-September/000734.html):

(...) This is my proposal to be discussed:


THE GOAL:

Boost the overall String concatenation / append operations.


BACKGROUND / HISTORY:

  • At the beginning (JDK 1.0 days) we had String.concat() and StringBuffer to build Strings. Both approaches had initially bad performance.
  • Starting at JDK 1.4 (I think), a share-on-copy strategy was introduced in StringBuffer. The performance gain was obvious, but increased the needed heap and in some cases produced some memory leak when reusing StringBuffer.
  • Starting at JDK 1.5, StringBuilder was introduced as the “unsyncronized version”, but also the copy-on-write optimization was undo, becoming an "always copy" scenario. Also, the String + operator is translated to StringBuilder.append() by javac. This has been discussed but no better alternative was found (seehttp://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6219959 ).
  • This current implementation generates several System.arraycopy() calls: at least one per append/insert/delete (two if expanding capacity)... and a final one in the toString() method!


STUDYING THE USES:

  • If we look at the uses of StringBuilder (both inside JDK code, in application servers and/or final applications), in nearly 99% of times it is only used to create a String in a single-threaded context and (the most important fact) only using the append() and toString() methods.
  • Also, only in 5% of the instantiatings, the coder establishes the initial capacity. Many times doesn’t matter, but other times it is impossible to guess it or calculate it. And even worst: some times the coder fails in his guess: establishes to much initial capacity or too few.


MY PROPOSAL:

  • Create a new class java.lang.StringAppender implements Appendable.
    1. Mostly same in its exposed public constructors and methods than StringBuilder, but the only operations are the “append()” ones (no insert, no delete, no replace).
    2. Internally represented as a String array.
    3. Only arraycopy() or create char arrays once, inside the toString() method (well, this isn’t completely true: also arraycopies when appending objects/variables other than String instances or char arrays, but the most typical operation is appending strings!).
    4. Doesn’t need to stablish an initial capacity. Never more calculating it or guessing it.
  • Add a new constructor in the java.lang.String class (actually 5 new constructors for performance reasons, see below):
    1. public String(String... strs)
    2. public String(String str0, String str1)
    3. public String(String str0, String str1, String str2)
    4. public String(String str0, String str1, String str2, String str3)
  • (NOTE: these 3 additional constructors are needed to boost appends of a small number of Strings, in which case the overload of creating the array and then looping inside is much greater than passing 2, 3 or 4 parameters in the constructor invocation).
  • Change the javac behavior: the String + operator must be translated into“new String(String...);instead of into “new StringBuilder().append().append()... ..toString();”.
  • Revise other JDK sourcecodes to use StringAppender, and the rest of programs all around the world. (By the way in the Glassfish V2 sourcecode I see several String.concat() invocations; seems strange to me... ).
  • So the new blueprints for String concatenation should be:
    1. For append-only, not conditional String concatenations, use the new String constructor. Example: String result = new String(part1, part2, part3,part4);
    2. For append-only, conditional or looped concatenations, or for appending other Objects/types, use the StringAppender class.
    3. For other manipulations (insert, delete, replace), use StringBuilder.
    4. For a thread-safe version, use StringBuffer.


THE BOOST:

As you can see in my microbenchmark results, executed in Linux x64 and Windows 32 bits (-server, -client, and -XX:+AggressiveOpts versions), we can achieve a boost between 1% and 167% (depends on the scenario and architecture). Well, those values are the extremes, the typical gains go between 20% and 70%. I think these results are good enough to be taken intoconsideration :-)


THE SOURCE CODE:

See attachments, String.java.diff with the added code (it is clear), and StringAppender.java with the new proposed class.


THE MICROBENCHMARK CODE:

See attachment. Of course should be revised. I think I have made it correctly.


THE MICROBENCHMARK RESULTS (varied to me about +/-1% in different executions due to the host load or whatever):

See attached file. I think they are great...


What do you think?
Best regards,



Y este ha sido de momento :-) mi último mail de respuesta (http://mail.openjdk.java.net/pipermail/core-libs-dev/2008-September/000747.html)...

Hi all.

I have measured the number of AbstractStringBuilder method invocations when executing NetBeans 6 (and making some work for 5 minutes) and when executing for a while a Glassfish V2 with a typical web application (Struts+Spring+Hibernate). These are the interesting and empirical results:


My conclusions:

  • The number of append(char) is bigger than append(String) and much bigger than other inserts (mainly in the Netbeans execution), so my StringAppender approach wouldn't perform good as predicted by Rémi and others. But the number of append(int), append(float), append(Boolean) is very low.
  • In a very few situations a Builder/Buffer is reutilized after calling toString().
  • expandCapacity() is invoked a lot of times!!!
  • Other operations (different than appends) are invoked a very few times.

So I suppose that my two proposals have few sense in general (haven't they?), but maybe these figures can help to reconsider the value of the default buffer length or can help others trying to make String/StringBuilder faster. Heap allocation and System.arraycopy calls must be reduced anyway!

P.S: Nevertheless I attach the "final" version of my modification to String.java and of StringAppender.java, which has some optimizations to append(char) but not sufficient... I think.

Best regards,


A mi personalmente me frustra la cantidad de alojamientos de heap y copias de arrays que podrían ser innecesarios en muchos casos, pero parece complejo encontrar una solución más eficiente... Bueno, en realidad tampoco es que se trate de un desastre absoluto porque afortunadamente los arraycopys están muy optimizados al menos en Solaris, Linux y Windows (me consta que ha habido mucho trabajao conjunto de los ingenieros de Sun con los de Intel y los de AMD)...

Y por otro lado la solución (sea la que sea) tiene que ser una solución "universal" en el sentido de que el código se migre mayoritariamente desde StringBuilder a StringAppender (o mejor, optimizando StringBuilder claro), como pasó en la migración masiva de StringBuffer a StringBuilder en los tiempos de la 1.5... o si no nuestro querido Hotspot puede decidir no compilar nativamente todos los sus métodos (qué asco, pero esa historia y lo chulos que son los compiladores nativos de verdad merece un post aparte).

Es decir, la mencionada nueva clase podría también crearse en Apache Commons o dentro de mis proyectos, pero si se usa millones de veces StringBuilder, malamente la JVM va a pensar que StringAppender tiene sus "hotspots"...

Por cierto que hay otras inciativas de optimizar las clases AbstractStringBuilder, StringBuilder y StringBuffer, pero van en otra línea más de inlinings y esos rollos. Y en alguna de ellas voy a echar una mano en pruebas microbenchmarkings, si sale algo positivo ya os contaré...

Y vosotros ¿qué opináis? ¿o soy yo el único que le ve interés a este asunto del rendimiento de los Strings????

:-)