viernes, 26 de diciembre de 2008

YSlow

http://developer.yahoo.com/yslow/

Complemento para Firebug que analiza las página según las best practices de rendimiento de sitios web del equipo Exceptional Performance de Yahoo.

viernes, 19 de diciembre de 2008

webpagetest.org

Me hago eco de esta reseña de la herramienta http://www.webpagetest.org/ que Martín hace en su blog.

No la he probado pero tiene muy buena pinta. Ahí está el artículo: http://brigomp.blogspot.com/2008/12/probando-el-rendimiento-de-pginas-web.html

domingo, 14 de diciembre de 2008

highscalability.com

Gracias a este post en el blog de Pensamientos Ágiles he descubierto uno de los mejores blogs/sites relacionados con la escalabilidad y cloud computing:

http://highscalability.com/

Artículos muy buenos y los enlaces también. Por cierto, muy chula la serie de artículos "Real Life Architectures", con notas acerca de la arquitectura de Amazon, Google, Flickr, Twitter, YouTube, eBay, Digg, MySpace...

P.D: Ya estoy viendo a mi amigo cmaJ haciendo palmas con las orejas por poder cotillear sobre estos grandes... ;-)

martes, 9 de diciembre de 2008

Eliminar ETags para incrementar el rendimiento

Esto es algo que sigo sin entender, y que encima está por defecto en Apache: si para comprobar que es correcta la versión de un contenido que el navegador tiene cacheada se envía la cabecera "If-Modified-Since"... ¿por qué además existen los headers ETag en respuestas y posteriores If-None-Match en peticiones? (Nota para despistados: el ETag es como una especie de digest cutre del fichero).

Y para más coña, Apache lo genera por defecto a partir del INode, Fecha y Tamaño del fichero en cuestión. Desde mi punto de vista no aporta nada, consume recursos, obliga a enviar cabeceras innecesarias, y además puede dar "falsos negativos" en granjas o entornos balanceados o después de un despliegue de una aplicación (mismo fichero pero en otro inodo!).

iPlanet / Sun ONE Web Server (al menos hasta la versión 6.1) también lo pone por defecto y también se recomienda eliminarlo para mejorar el rendimiento.

Pero además, en Apache me he encontrado con otro problema: si está activado el mod_deflate para la compresión de contenidos, Apache nunca se da por enterado de que el fichero solicitado sí es el mismo que tiene el usuario en su cache, así que siempre devuelve un "200 OK" + todo el contenido, en lugar de un "304 Not Modified". Es porque al ETag enviado al cliente se le añade el sufijo "-gzip" al ETag, y es lo que el cliente envía de vuelta... pero por lo que sea Apache no es capaz de entender que el ETag con GZIP debería ser el mismo que el ETag sin GZIP. Es decir, da igual cómo se haya transportado el contenido, lo importante es si lo que hay en el servidor es lo mismo que tiene la caché del cliente ¿o no?

Bueno. ¿Y cómo se elimina? Afortunadamente de forma muy sencilla (para Apache):

FileETag None

Y para iPlanet 6.1 también es trivial:

<Object name="default">
   (...)
   Output fn="set-variable" remove-srvhdrs="etag"
</Object>

Para más información de por qué esto mejora el rendimiento:

Dejo por aquí también referencia al RFC de HTTP/1.1 en lo que a cabeceras se refiere. Cuántas veces he tirado de él y qué poco de moda está a día de hoy tener tiempo para leer RFCs, sino mejor buscar en blogs... ;-)

viernes, 5 de diciembre de 2008

martes, 2 de diciembre de 2008

Ahora sí: Java 6 update 11

Pues unas horas después de la salida nula... ya está disponible el 6u11 en http://java.sun.com/javase/downloads/.

Como os comentaba, y podéis ver en las notas de la versión, incluye:
  • 13 fixes de vulnerabilidades de seguridad.
  • 18 fixes de otros bugs, sólo uno de Hotspot, y principalmente relacionados con los gráficos y el plugin (vamos, los grandes cambios de la 6u10 en ambos aspectos han generado alguna cagadilla)
  • 16 fixes en VisualVM

Así que, parece que aunque la excusa de los updates impares son actualizaciones de seguridad, este en concreto lo han tenido que adelantar para arregar alguna cagadilla de la última release de hace 45 días.

Espero que la política de Sun no cambie hacia la "beta permanente" que está poniendo de moda Google y que amparamos bajo el nombre genérico de metodologías ágiles... y que tiene sentido en servicios/aplicaciones modelos SaaS... ¡pero no en esto, coño!

Salida nula: Java 6 Update 11 (6u11)

Ummm...

Pues ha debido haber alguna "salida nula" en Sun o algún bug detectado a última hora, o alguien que ha tenido el dedo del gatillo demasiado rápido.

Porque en algún momento entre el día 1 y el 2 de diciembre (hoy) Sun ha liberado el Update 11 (6u11) del JDK/JRE pero a continuación lo han vuelto a eliminar de la página de descargas.

He visto la reseña en este post: http://blogs.sun.com/nbprofiler/entry/jdk_6_update_11_comes y efectivamente como indica alguno de los comentaristas si googleas "Java SE Development Kit (JDK) 6 Update 11" ya está indexado...

Como comentaba en la entrada Schedule de siguientes updates de Java 6. Versiones de Hotspot y compatibilidad entre ellas, se esperaba que este 6u11 sóo contuviera actualizaciones de seguridad pero por lo que parece incluye también al menos una actualización de VisualVM. Pero tiene que haber alguna actualización de seguridad porque si no, no se explica esta aceleración en el tiempo entre updates. ¿Menos de 2 meses? Ummm...

jueves, 27 de noviembre de 2008

HOWTO: Importar un certificado servidor de Apache en un almacén JKS

[Offtopic: Esta entrada no habla sobre rendimiento]

Si tenemos un servidor Apache configurado para atender por SSL con su certificado servidor y su clave privada, y queremos utilizar el mismo certificado servidor en una aplicación Java, ¿cómo hacerlo?

Usos: por ejemplo si queremos eliminar Apache como proxy inverso y que nuestro motor de servlets (Glassfish, Tomcat o lo que sea) sea el frontal web directamente, o si queremos utilizar el mismo certificado que tenemos en el Apache para otros propósitos como por ejemplo en un servidor FTPS hecho en Java (como Apache Mina) o cualquier otro servicio paralelo.

Para ello lo más sencillo es utilizar un PKCS#12 pivote, que luego podremos eliminar.

Comandos:

$ openssl pkcs12 -export -in <certificado.cert> -inkey <claveprivada.key> -out <pkcs12.p12>
$ keytool -importkeystore -srckeystore <pkcs12.p12> -srcstoretype PKCS12 -destkeystore <almacen.jks> -deststoretype JKS

Obviamente, reemplazar <certificado.cert>, <claveprivada.key>, <pkcs12.p12> y <almacen.jks> por los nombres adecuados (ficheros origen utilizados por nuestro Apache, y JKS destino que se utilizará por nuestra aplicación Java).

Nota: la opción -importkeystore sólo es válida a partir de Java 6. En JDKs antiguos se utiliza "-import -pkcs12" (creo, pero tendría que tirar de archivo...)

domingo, 23 de noviembre de 2008

Compatibilidad entre versiones de Hotspot y versiones del JDK

Este es mi descubrimiento más curioso de todos en los últimos meses. Lo que en realidad me resulta curioso es no haberlo sabido/intuido hasta ahora. Es bueno estar siempre aprendiendo cosas nuevas, y es bueno también bajarse los humos uno mismo de vez en cuando...

Tradicionalmente muchos suelen confundir JDK y JVM. Pero son dos cosas distintas, una cosa es la versión de las bibliotecas Java, versión de bytecode y compilación javac (JDK) y otra muy distinta es la máquina virtual que ejecuta las aplicaciones Java. Esto lo he tenido claro siempre, lo que no sabía es que Hotspot tiene compatibilidad hacia atrás con versiones anteriores del JDK...

Con JRockit, esta diferenciación siempre ha estado muy clara, tanto por los usuarios como sobre todo por el equipo de JRockit/Bea/Oracle/quien_les_compre_a_continuación. Ellos distribuyen una versión de su VM "empaquetada" junto con una versión del JDK. De hecho, la versión JRockit 27.5 es compatible con JDK 1.4, JDK 1.5 y Java 6. Misma VM, diferente JDK en el paquete.

Pero estas semanas he estado haciendo pruebas y hablando con los ingenieros de Sun, resulta que Hotspot también es independiente del JDK y compatible hacia atrás con todos ellos (desde JDK 1.7 hasta JDK 1.5). En concreto he conseguido hacer funcionar con éxito y sin problemas aplicaciones sobre JDK 1.5 con Hotspot 14-b05 (distibuido en JDK 7 early access), y también el 6u6p con el mismo Hotspot 14 en lugar de el 13, que es el que venía por defecto...

¿Cómo se hace? Sencillo. Basta con identificar la bilioteca jvm de nuestro JRE y reemplazarla por la deseada. Algunos ejemplos:

  • En Windows, es el fichero jvm.dll que se encuentra tanto en <jre>\bin\server como en <jre>\bin\client (ojo, por supuesto que en realidad son distintas versiones, Hotspot cliente y Hotspot servidor)
  • En Linux x64, es el fichero libjvm.so que se encuentra en <jre>/lib/amd64/server

Del mismo modo, si no recuerdo mal, podemos poner la biblioteca jvm de JRockit en un directorio de instalación del JRE de Sun y viceversa. Es decir, la VM (Hotspot, JRockit, etc) es simplemente el libjvm.so si no me equivoco!

Lo que desde ya te puedo asegurar es que modificar la combinación JDK-JVM no está soportada por Sun. Así que, como suele decirse en estos casos... "Do it at your own risk!". Esto es lo que me dicen desde el equipo de Hotspot extraoficialmente cuando he preguntado por ello:

"Yes, for practical purposes, the latest HotSpot VMs should be compatible with JDKs back to 1.5, and we in engineering try hard to make that possible.

Sun, as a company, doesn't guarantee complete backward compatibility because of the huge testing matrix that would re required to verify each HotSpot release with every prior JDK. (We support more many more platforms than JRockit.)

So a customer can try out an new VM with an old JDK, but don't run your enterprise on it, and don't ask Sun to support it. ;-)"

:-)

martes, 18 de noviembre de 2008

Schedule de siguientes updates de Java 6. Versiones de Hotspot y compatibilidad entre ellas

Como veis, estoy dosificando (demasiado) la información que he obtenido de mi interacción con el equipo de ingenieros de Hotspot en Sun, que ha sido muy interesante y emocionante al mismo tiempo.

Como diría un amigo mío, a mi edad y aún sigo emoocionándome con chorradas... :-)

Y creo que ahora estoy dando una primicia.

Ni que decir tiene que esta información es absolutamente extraoficial (aunque probablemente el calendaro previsto sea público a colaboradores, seguro que está por https://jdk6.dev.java.net/ pero no he tenido tiempo de chequearlo) y que el calendario final puede variar por muy distintos motivos. Tampoco aseguro que la información sea verídica, es la que me han pasado desde dentro, eso sí que lo aseguro...

Calendario previsto de siguientes updates de Java 6:

  • Java 6 update 10 (6u10 / 1.6.0_10): recién liberado el 15 de octubre, con la nueva microarquitectura
  • Java 6 update 11 (6u11 / 1.6.0_11): reservado para posibles actualizaciones de seguridad, puede que no se produzca si no es necesario
  • Java 6 update 12 (6u12 / 1.6.0_12): planificado para Febrero o Marzo de 2009
  • Java 6 update 13 (6u13 / 1.6.0_13): reservado para posibles actualizaciones de seguridad, puede que no se produzca si no es necesario
  • Java 6 update 14 (6u14 / 1.6.0_14): Planificado para Mayo o Junio de 2009

Correspondencia entre versiones de Hotspot y distribuciones de Java 6:

  • Hotspot 10: incluido desde Java 6u04 (con diferentes builds y fixes en sucesivos updates u05, u06 y u07).
  • Hotspot 11: ya incluido en Java 6u10.
  • Hotspot 12: ¿? creo que nunca se liberará, por mis noticias es una vía muerta.
  • Hotspot 13: fue incluido en Java 6u6p ("performance release") a modo de anticipo de futuras mejoras de rendimiento, pero tiene algunos bugs reconocidos como el que indiqué en el anterior post. Dudo que lo incluyan en 6u12, seguramente dará paso a Hotspot 14 directamente...
  • Hotspot 14: su build 05 está actualmente incluido en Java 7 (desde algun snapshot relativamente reciente, no recuerdo cuál) y está previsto su lanzamiento para entornos productivos con Java 6 Update 14 (6u14). Es posible que se libere en 6u12 si consiguen solucionar ciertos problemas de estabilidad que actualmente tienen, aunque lo más probable es que no sea así y tengamos esperar a mayo-junio pues...

O lo que es lo mismo, salvo que haya fixes de seguridad, tengo bastante claro que dejaré mis entornos de producción estables con 6u07 hasta junio (quizás actualice a 6u10 en enero). Y entonces migrar en los entornos de Pruebas y comenzar la certificación... o sea, que preveo que actualizaré Producción después de verano, dando el salto desde Java 6u7 a 6u14...

:-)

lunes, 17 de noviembre de 2008

"What volatile means in Java" exposed by Jeremy Manson

Para quienes no sepan de él, Jeremy Manson es una de las eminencias y referencias en el mundo de la concurrencia Java y en el modelo de memoria Java, de hecho, fue uno de los papás del "nuevo" modelo de memoria Java allá por 2004-2005. Curra en Google, lo cual dice algo sobre su nivel, y hay a quien se le hace el culo pepsicola con estas cosas... (por eso lo menciono :-) Y es un tipo excepcionalmente abierto y amable aun cuando sabe que no tienes razón en lo que le expones, podéis buscar mis interacciones con él en su blog :-)

Jeremy ya lo expuso bastante bien en sus anteriores posts (que yo referencié aquí), y en realidad este no da excesiva información adicional. Pero está bastante bien explicado y con algún gráfico que se agradece...

Así que no me enrollaré mucho más. Ahí van los enlaces a sus tres posts acerca de las variables volátiles en el modelo de memoria Java:

Y esta es una gran PPT sobre el modelo de memoria en Java, escrito por el propio Manson et al.:

P.D: He actualizado también el post de julio sobre double-checked-locking con esta información...

miércoles, 12 de noviembre de 2008

Uso de Apache como proxy inverso de Glassfish V2

Aunque en muchos escenarios sería más que discutible... una práctica bastante habitual es tener un proxy inverso que sirva el contenido estático, haga balanceo y algunas reglas de seguridad delante de nuestro Glassfish.

Como digo es algo muy discutible en el 75% de los casos, la mayor parte de al gente lo hace como "buena práctica" y punto, pero no siempre hace falta y a cambio se complica un poquito más el sistema... Si se hace sólo por rendimiento, a día de hoy no tiene sentido porque Grizzly (el motor http de Glassfish) es muy óptimo y lo que se gana por un lado se pierde en las comunicaciones entre el proxy inverso y la aplicación final. Pero si se hace por seguridad (suele ser la excusa pero en la mayor parte de los escenarios no aporta nada, sobre todo si se ejecuta en la misma máquina que el Glassfish, je, je), o por balanceo de cargas o por caches, etc, etc. Entonces me callo. Sí que hace falta. Y es muy buena idea que sea Apache el servidor web escogido.

En cualquier caso. ¿Cómo hacer que un Apache 2 / 2.2 sea el frontal HTTP para nuestro Glassfish?

Espero que en V3 den soporte bien dado, para Glassfish V2 hay que hacerlo con mod_jk en el lado Apache y copiando unos jars de Tomcat 5.5 en el lado Glassfish. Sí lo habéis leído bien...

En todos estos enlaces están las instrucciones:

Pero, oh sorpresa, resulta que en los últimos meses las cosas han cambiado. Tomcat 6 no trae ningún tomcat_apj.jar (sino que lo han integrado en tomcat_coyote.jar) y la implementación en las versiones más recientes de Tomcat 5.5 no es compatible con Glassfish V2. ¿Te sale esta excepción al hacer una request?:


java.lang.NoSuchFieldError: USE_CUSTOM_STATUS_MSG_IN_HEADER
at org.apache.jk.common.JkInputStream.appendHead JkInputStream.java:283)
at org.apache.jk.core.MsgContext.action(MsgContext.java:267)
at org.apache.coyote.Response.action(Response.java:221)
at org.apache.coyote.Response.sendHeaders(Response.java:416)
at org.apache.coyote.tomcat5.OutputBuffer.doFlush(OutputBuffer.java:355)
at org.apache.coyote.tomcat5.OutputBuffer.close(OutputBuffer.java:321)
at org.apache.coyote.tomcat5.CoyoteResponse.finishResponse(CoyoteResponse.java:578)
at org.apache.coyote.tomcat5.CoyoteAdapter.afterService(CoyoteAdapter.java:318)


Si esta es la excepción, esta es tu solución: coge el tomcat_apj.jar de Tomcat 5.5.23 y no de ninguna posterior (actualmente van por la 5.5.27 en la rama 5.5). Y funciona perfectamente.

Al César lo que es del César, a mi me ha salvado la vida esta entrada: http://www.albeesonline.com/blog/2008/10/10/javalangnosuchfielderror-use_custom_status_msg_in_header/

Todo una guarrada, ¿verdad? Lo cierto es que dice poco del equipo de diseño de Glassfish, es como si no hubieran pensado en implantaciones "para el mundo real"... ¿?

sábado, 8 de noviembre de 2008

Identificación de bugs en la versión 6u6p y workaround

Como indiqué en el post 6u6-p: Pues en Linux x64 peta!!!!!!!, en mis entornos servidor tuve problemas en la versión 6u6p. Tras enviar el feedback a Sun, los ingenieros del Hotspot se pusieron en contacto conmigo e hicimos múltiples pruebas.

El trato ha sido maravilloso, y estoy encantado de haber podido ayudar a identificar el problema, aunque he de decir que ya lo tenían resuelto, je je.

Tras varias semanas, por fin hemos llegado a la conclusión de que se trataba de un problema con el análisis de escape (ver para más información sobre Escape Analysis, ver los posts Allocation, deallocation and escape analysis for Java y ¿Funcionan las optimizaciones sobre el modelo de threading? ), que ya estaba identificado en el bug 6726999 y que han resuelto en la versión 14 de Hotspot, cuyo build 05 por cierto se distribuye con los Early Access del JDK 7 y que está previsto que se incorpore en Java 6 en un update aproximadamente en mayo-junio. Habrá que esperar pues...

He aprendido varias cosas de las interioridades de Hotspot y de versiones, pero tengo otros posts preparados para eso... De momento sólo diré que con el nuevo 6u10 por fin se distribuye Hotspot 11, pero el desarrollo del mismo ya va por la versión 14... Como podréis imaginaros, la evolución a nivel de Hotspot está siendo espectacular aunque tenemos que esperar para ver cada versión en la calle...

Me han pasado también varias versiones del libjvm.so, en concreto versiones "fastdebug" que tracean información de ejecución y compilación, y tiene además toneladas de asserts. Por ejemplo, algunos parámetros de lanzamiento interesantes para las VM fastdebug:
  • -XX:+LogCompilation: para escribir en un fichero hotspot.log y en el especificado arriba información acerca de la compilación binaria en tiempo real
  • -XX:SuppressErrorAt= (por ejemplo -XX:SuppressErrorAt=/xmlstream.cpp:119) para que no se ejecute un cierto assert
A lo que íbamos. Aparte de todo este rollo, el único workaround de momento, si se quiere funcionar con la 6u6p en producción es ejecutarlo con el parámetro de lanzamiento -XX:-DoEscapeAnalaysis. Yo no voy a hacerlo, prefiero seguir con Java 6u07 en producción pero con esa opción habilitada (porque ejecuto bastantes clases compiladas para JDK 1.4, aggg, y está lleno de StringBuffers, que se benefician bastante de los análisis de escape, aunque en Hotspot 10 estén hastante limitados de momento.

Además, dejo una perlita... en toda la interacción con el equipo de ingenieros de Hotspot, me han dado información extraoficial acerca de fechas previstas de liberación de las siguientes actualizaciones de Java 6 y sus correspondencias con versiones de Hotspot. Esto lo publicaré en el siguiente post...

:-)

lunes, 3 de noviembre de 2008

Lanzamiento de Java 6u10

[Actualizado el 6/11/2008: algunas correcciones y ampliación del resumen ejecutivo con mis conclusiones]

Sun liberó el pasado día 15 de octubre por fin Java 6 update 10 (6u10), que puede descargar como siempre de http://java.sun.com/javase/downloads/, tras mucho tiempo en beta y release candidate...

Nota: la noticia tiene ya 15 días, pero he preferido no escribir sobre ello hasta haberlo probado, claro :-)

Se trata de un hito importante (llevaban en beta bastante tiempo) porque supone una pequeña revolución tecnológica al menos para usos "de escritorio". De hecho, si os fijáis ha habido un salto entre el anterior update (6u07) y este (6u10), es debido a que se empezarons a librerar versiones beta más o menos a la vez que la 6u06, y dejaron margen a updates de seguridad por si se retrasaba más de lo esperado.

Pueden verse los detalles en:

Aquí mi resumen ejecutivo:

  • No se incluyen actualizaciones resoluciones de vulnerabilidades de seguridad. Por lo tanto la versión 6u7 está igual de actualizado que 6u10 en este aspecto.
  • Resolución de múltiples bugs, la mayoría afectando al uso de escritorio, entorno gráfico e instalación, pero algunos también son temas estrella en las versiones servidor, por ejemplo por fin han eliminado la limitación en el tamaño de heap por defecto a 1,4 GBytes en Windows x64 (se mantiene en Windows x32 por la limitación del espacio de direcciones a 2 gigas), además de corregir un memory leak con el modelo ParalellOlgGC de Garbage Collector (es el modelo paralelo que utilizo yo).
  • Se incorpora por fin la versión 11 de Hostpot, desde 6u4 se mantenía la versión Hotspot 10, con mejoras de rendimiento tanto para cliente como para servidor, además de resolución de bugs como los mencionados arriba
  • Nueva implementación del renderizado Direct3D para Windows, incluyendo (¡por fin!) aceleración hardware, aunque de momento sólo esté implementada para tarjetas ATI u nVidia).
  • Refactorización del JRE. El JRE ahora consta de un núcleo (kernel) mínimo y se instalan distintos bundles en función de las necesidades (ver documentación para más detalles).
  • Reingeniería del Plugin. Menudo revolcón le han dado, ya no es un pluggin "para apllets Java" sino que además integra las nuevas tecnologías JNLP y JavaFX. Importante: La gestión de applets tiene cambios significativos, incluyendo el tratamiento de applets no firmados, ver las release notes...
  • Y ¡por fin!, mejor control de versiones, elección de qué versión usar en caso de tener varias instaladas... y lo más importante de todo: la actualización puede funcionar en modo parche para no tener varios directorios con distintos updates del JDK instalado. Con lo molesto que era hasta hahora consolidar todo esto a mano. Por cierto que manda huevos que no lo hayan hecho hasta ahora.
Yo ya lo tengo instalado en mi PC, y funcionando con Netbeans y SQLDeveloper sin problema. Por cierto que he actualizado el post Tuning de NetBeans 6 (y NetBeans 6.1) para reflejar mi nueva configuración con aceleración gráfica por hardware, je je.

También lo hemos instalado ya en los entornos servidor de Desarrollo y Pruebas Integradas. Sin embargo, de momento no lo voy a instalar en los entornos de Preproducción y Producción hasta dejar pasar un tiempo y poder hacer pruebas de carga. En función de los calendarios que maneje de aquí a unos meses es posible que no tenga tiempo de certificarlo y, además, tengo noticias de primera mano "desde dentro" del equipo de Hotspot acerca del calendario de siguientes actualizaciones y versiones de Hotspot incluidas en ellas... pero eso es otra historia que escribiré en otro post ;-)

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????

:-)

viernes, 26 de septiembre de 2008

Oracle: modo "Servidor Dedicado"

Para los "listillos" en Oracle, como yo. Es decir aquellos que no somos DBA sino que nuestra experiencia se basa más en Java, pero que nos ha tocado alguna vez configurar Oracle.

Y claro, es un mundo. Y claro, las opciones aparentemente más lógicas no son las mejores.

En concreto me ha pasado con la configuración de Shared/Dedicated Server. En las instrucciones de creación de base de datos dice lo siguiente:
  • Modo Servidor Dedicado: "para cada conexión cliente la base de datos asignará un recurso exclusivo (...). Utilice este modo cuando el número total previsto de conexiones cliente sea pequeño o los cuando los clientes realicen solicitudes reiteradas y de larga duración a la base de datos. (...)"
  • Modo Servidor Compartido: "varias conexiones cliente comparten un pool de recursos (...). Utilice este modo cuando el número de usuarios que dan conectarse simultáneamente a la base de datos sea considerable a la vez que se utilizan eficazmente los recursos del sistema. (...)"
A priori, ¿cuál eligiríais? Pues no.

La cuestión es qué se considera "pequeño" en cuanto al número de conexiones. Y qué hardware tenemos. Y si el servidor Oracle compite por recursos con otros procesos importantes en la misma máquina. Y cómo se conectan las aplicaciones a la base de datos.

En resumidas cuentas, el modo compartido es un pool de conexiones. Pero nuestras aplicaciones (Java o lo que sea) ya gestionan las conexiones a través de uno o varios pooles por lo que lo único que estamos haciendo es anidar pooles. O lo que es lo mismo, en realidad en un escenario como el mío estoy tirando recursos y rendimiento a la basura. Cada shared server consume unos 20 megas de memoria adicional y, al fin y al cabo, termina abiendo conexiones reales dedicadas, claro.

Esquema del modo dedicado (Dedicated):

Esqueña del modo compartido (Shared Servers):


Por regla general podemos considerar que si vamos a tener menos de 500-600 sesiones abiertas y además hay pooles de conexiones en los servidores de aplicaciones cliente, se use el Modo Dedicado. En caso de un volumen mucho mayor el modo compartido.

¿Y cómo cambiar de un modo compartido a uno dedicado?. En Oracle 10g y superiores:
$ sqlplus system as sys dba
SQL> alter system set processes=400 scope=spfile;
SQL> alter system reset sessions scope=spfile;
SQL> alter system set shared_servers=0 scope=spfile;
SQL> alter system set max_shared_servers=0 scope=spfile;
SQL> alter system reset dispatchers scope=spfile;
Donde dice "400", establézcase el valor que corresponda. Si no se establece valor para sessions, el sistema lo tomará de la siguiente fórmula: sessions = (processes * 1.1) + 5.

Puesto que estos valores son estáticos, debe reiniciarse la instancia para que tome su valor.

¿Y cómo se consultan los valores actuales?:
SQL> show parameter [parametro]
P.D: Para los que conocen el init.ora y no los SPFILES, las cosas han cambiado, ahora los parámetros se establecen a través de estos comandos de sistema...

domingo, 21 de septiembre de 2008

6u6-p: Pues en Linux x64 peta!!!!!!!

[Actualizado el 8/11/2008: El problema radica en un bug en el Escape Analisys, para más información ver Identificación de bugs en la versión 6u6p y workaround ]

Actualización al post Java SE 6u6-p Performance Release - YA DISPONIBLE EN OTRAS PLATAFORMAS:

Al lanzar Glassfish, mientras se despliegan mis aplicaciones (gordas eso sí), aproximadamente en el 70% de las ocasiones se me ha producido un core con un correspondiente fichero de err_.log. Lo curioso es que en el 30% restante de las veces ya no se cae, aunque pase pruebas de stress... Lo he probado tanto en CentOS 5.1 sobre un Supermicro como en un Redhat ES sobre un Sun Fire x64 y en ambos ocurre lo mismo.


He reportado la incidencia y los ingenieros de Sun están en contacto conmigo par resolverla... pero vamos, me ha dejado el asunto un poco frío porque en la propia web de Sun dice que la versión puede ser utilizada en producción y que puedes pedir soporte. Vamos, que en ningún momento tenía la sensación de que fuera a hacer de beta-tester, aunque lo hago bien a gusto. Digo esto como una crítica "suave", porque todos los del gremio estamos expuestos a estos fallos...

Pero nuestro gozo en un pozo de momento. Así que volvemos a la confortable versión 1.6.0_07...

martes, 16 de septiembre de 2008

Aceleradores hardware

[Nota: en este artículo hago publicidad de marcas y modelos que conozco, sin otro ánimo que el ilustrativo y en ningún caso quiere decir que alternativas de la competencia sean peores opciones].

Actualizado el 19/9/2008


Con los avances en paralelización y multithreading de los últimos años (aunque con un cierto estancamiento de la velocidad por core), actualmente los responsables de servicios podemos adquirir servidores muy potentes a un coste relativamente bajo si lo comparamos con años anteriores. Ejemplos como Supermicro dan opciones muy económicas para entornos de "no tan misión crítica", pero fabricantes como HP e incluso Sun dan opciones x64 tremendamente interesantes (donde sube el precio es en el soporte).

En cualquier caso, hay situaciones que requieren "algo más". Y me explico. Si tenemos un entorno web de mucha concurrencia y computacionalmente "denso" puede ser que con un crecimiento horizontal en granja no sea suficiente. O mejor dicho, para mi gusto hay otras opciones que no requieren incrementar el número de máquinas.

Me estoy refiriendo a los aceleradores hardware. Los principales usos son la aceleración de SSL (operaciones de criptografía simétrica y asimétrica específicas para comunicaciones), HSM (operaciones criptográficas de propósito general) y GZIP (para la compresión de contenidos como se menciona en el post Compresión HTTP). He visto en una gran entidad financiera, con mis propios ojos, "morirse" un pedazo de servidor cuando hacía las operaciones SSL en los procesadores de propósito general, creedme.

Es cierto que muchos balanceadores y otros appliance de red nos solucionan parte de esta problática, pero en aquellos casos en los que esto no es una opción (o en los que precisamente lo que estamos construyendo es un appliance o proxy inverso) lo que debemos es adquirir los elementos hardware aceleradores adecuados. Hoy en día las opciones son mucho más económicas y mucho más potentes que lo que mucha gente se piensa.

Algunas opciones que hay en el mercado y que yo he tenido la oportunidad de probar (y poner en producción) son:
  • nFast SSL Offload (de nCipher): Tarjeta de red PCI (10/100/1000) que realiza las funciones criptográficas tanto asimétricas como simétricas (esta segunda parte es relativamente novedoso) necesarias para dar servicio SSL. Lo interesante de esta tarjeta es que actúa como un proxy inverso interno. Es decir, el interfaz de red atiende mediante canal SSL pero nuestro software servidores lo configuramos para que escuche "en plano", por lo que su integración es limpia e inmediata, y compatible con cualquier software.
    • Rendimiento nominal: 10.000 transacciones SSL/TSL por segundo
    • Throughput: 300 MBit/sec Full Duplex
  • AHA363 (de Comtech AHA): Tarjeta PCIe aceleradora GZIP, la más rápida del mercado. Trae una API y un módulo para Apache (aunque pueden desarrollarse otros.
    • Throughput: 5 GBit/sec
  • nShield 500 F2 (de nCipher): HSM completo que implementa todos los algoritmos de criptografía simétrica y asimétrica (incluso curvas elípticas). No se trata sólo de acelerador sino que su uso principal es la securización de claves privadas. Dentro de toda la gama de velocidades y niveles de securización, esta es más que suficiente para la mayoría de los propósitos, pero hay otras muchas opciones.
    • Rendimiento nominal: 500 firmas RSA de 1024 bits por segundo.
¿Os imagináis un sistema de servidores frontales x64 con dos procesadores XEON Quadcore y que además incorpore estas tarjetas? Imbatible. Y sorprendentemente económico, en órdenes de magnitud los elementos que he enumerado anteriormente rondan unos PVP de 3000€, 1000€ y 5000€ respectivamente. Es decir, obviando el HSM estaríamos aproximadamente duplicando el coste de un servidor promedio de las características mencionadas. Por unos 8000-9000 € tendríamos este sistema que aceptaría una concurrencia salvaje.

Obviamente tiene también sus inconvenientes, y es que cada elemento hard adicional incrementa las posibilidades de fallo, por lo que hay que considerar spare units y todo lo que conlleva.

Por cierto, respecto a la aceleradora GZIP no sólo estoy hablando de usarlo en la capa de servidor web / frontal, sino que fácilmente podríamos adaptar las bibliotecas Java o C de compresión para que en nuestra aplicación también utilizáramos el hardware de aceleración, e incluso hacer un JDK adaptado...

En algunos casos, en realidad, no hace falta irse tan lejos. ¿Sabíais que muchas de las capacidades de nuestros procesadores modernos están infrautilizadas? Para algunos propósitos no hay mejor acelerador hardware que el procesador que ya tenemos. Tanto AMD como Intel tienen bibliotecas de alto rendimiento para operaciones de copia de arrays, matemáticas, otras útiles para compresión, cifrado, procesamiento multimedia, etc).

Estoy hablando de Intel Integrated Performance Primitives (IPP) y de AMD Performance Library (APL). Un colega mío utiliza IPP para tratamiento de señales y asegura que es una auténtica maravilla, de hecho lo definió como "los ingenieros de Intel van 20 años por delante nuestro".

Una de las cosas con las que algunas veces he "soñado despierto" es con que las JVM/JDK puedan estar adaptados a estos aceleradores y también utilizar esas bibliotecas de Intel y AMD. Incluso llegué a mantener una conversación con Henrik Ståhl (Product Manager de JRockit en el momento de la conversación) preguntándole/proponiéndole sobre algunas de esas especializaciones. No lo veía claro y además los costes de mantenimiento sería brutal.

Por cierto que no soy el único que lo ha pensado, como puede verse en este hilo: http://forums.java.net/jive/thread.jspa?messageID=224530.

Pero volviendo al mundo real y asumible, os aseguro que el uso de aceleración SSL es una prioridad y la aceleración GZIP una auténtica necesidad si tenemos una concurrencia tremenda (¿cómo lo hará Google Inc.? ¿con aceleradores hard con los procesadores genéricos de las nubes de servidores?)

P.D.1: No he nombrado aceleradoras de gráficos, o el uso de las GPUs porque es un mundo que desconozco y que no he necesitado...

P.D.2: Sólo he nombrado esas marcas y modelos porque son los que yo conozco / he tenido experiencia satisfactoria. Hay otros fabricantes como http://www.safenet-inc.com/ o http://www.indranetworks.com/ con soluciones probablemente igual de válidas que las yo nombro arriba...

Actualización 19/9/2008: En otra conversación reciente con Henrik Ståhl, me sorprendió diciéndome que el uso de código / bibliotecas específicas para cierto hardware no es algo que hayan descartado para JRockit, aunque sea complicado. Digo que me sorprende porque en la primera conversación fue bastante tajante sobre los problemas que supondría y dudaba de los beneficios, sin embargo ahora ha dejado la puerta abierta. A mi ego le gusta pensar que he podido servir de empujoncito/inspiración...

miércoles, 10 de septiembre de 2008

Glassfish V2: Performance Tuning, Tips & Tricks

[Nota octubre'08: Ver información complementaria en el post Tuning de motor de JSP en Glassfish: Configuración para entorno de producción ]

En la documentación oficial de Glassfish están bastante bien documentadas las opciones de rendimiento y optimización a varios niveles (a nivel de aplicación, de servidor de aplicaciones, de JVM y de parametrización de Sistemas Operativos): Este blog contiene información general muy interesante sobre rendimiento de Glassfish: Y en esta presentación hay una relación clara y concisa de puntos de optimización en Glassfish:
Elementos que contiene esa presentación, con mis anotaciones personales entre corchetes:
  • Tuning
    • Tuning básico a nivel de JVM [nada nuevo]
    • Tuning del Web Container [ojo que por defecto hay pocos threads y conexiones keep-alive]
    • Tuning del EJB Container [idem con los tamaños de los pooles]
    • Tuning de la configuración en alta disponibilidad [nada nuevo]
    • Tuning de Web Services y parseo de XML en general [muy interesante el uso de Woodstox, aunque aún no por defecto, creo que la integración todavía es experimental]
    • Recomendaciones generales [incluyendo la precompilación de JSPs, y deshabilitar todos aquellos elementos que no vayan a usarse]

  • Best practices
    • ... (mejor leer la PPT)
Es cierto que hay que tener mucho cuidado con tirar de PPTs en lugar de estudiar durante semanas, pero se agradecen este tipo de resúmenes ejecutivos, la verdad!

[Nota octubre'08: Ver información complementaria en el post Tuning de motor de JSP en Glassfish: Configuración para entorno de producción ]

sábado, 6 de septiembre de 2008

Copy-on-write optimization for StringBuilder and StringBuffer (II)

Continuación sobre Copy-on-write optimization for StringBuilder and StringBuffer.

Bien, parece que sí hay un bug en la Bug Database (no fui capaz de encontrarlo), y sobre el que se hizo un montón de investigación y pruebas, sobre JDK 1.5. Para más información, consultar http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6219959. Como se ve el bug se cerró como "Not fixed"a principios de 2006.

Las explicaciones para el cambio de estrategia de copy-on-write a always-write son, resumidamente:
  • Cuando se derivaban Strings desde StringBuffers en JDK 1.4, había un crecimiento importante en el consumo de memoria en los Strings. Si tengo un StringBuilder con tamaño de buffer N y el String final sólo ocupa N/2 posiciones, la estrategia de compartir el buffer hace que el String ocupe el doble de memoria de lo que debería, mientras que la nueva estrategia lo ajusta.
  • Aparte de esto, está el molesto tema de la inmutabilidad de los Strings. Si se hiciera mal uso de un StringBuilder (ojo, el problema no se daría en StringBuffer), podría darse el caso durante un pequeño lapso de tiempo de que de que un thread esté leyendo un String mientras que otro esté cambiando su contenido porque aún no se haya enterado de la compartición. Casi imposible en la práctica pero teóricamente posible, así que...

Queda el consuelo, leyendo la descripción del bug, de que los ingenieros de Sun pasaron muchas horas con el tema, que cualquier posibilidad que a mi se me iba ocurriendo ya la han considerado (gracioso, en el bug va diciendo "uno podría argumentar que...", "también podría pensarse en...", justo lo que yo había deliverado :-) ).

Y queda el consuelo de que tanto en el foro como el en bug se llega a la conclusión de que un escape analisis sería una solución válida para que Hotspot decidiera hacer la optimización cuando una regiiónd e código sólo es alcanzada por un thread a la vez. Otra cosa es si el esfuerzo merece la pena, claro...

Tengo una idea cojonuda en esta línea. Voy a darle unas vueltas y a proponerla. Tanto en este blog como en el foro de rendimiento.

viernes, 5 de septiembre de 2008

Copy-on-write optimization for StringBuilder and StringBuffer

Hoy he publicado mi primer post en el foro "General performance Discussion" @java.net:

http://forums.java.net/jive/thread.jspa?messageID=297309

Forums @ java.net
Pego directamente (en inglés, me ha costado lo mío, je je):
Hello everybody.

The old 1.4 version of StringBuffer had a very nice implementation of a "copy-on-write" (COW), [as have the C++ String and the .NET StringBuilder, I think]. In few words: when the toString() method is called, it doesn't copy the char array to the String, but shares it with the String and marks as "shared" inside; and if there are future call to insert or remove or others in the "immutable" section of the char array, then creates a copy of the array and goes on (so the String keep immutable and the StringB can mute.

For some reason, this changed in JDK 1.5, becoming in a "always copy" strategy. Why? Haven't find it. I have only found the forum entry dated in 2005:
http://forums.java.net/jive/thread.jspa?messageID=29032.

I don't understand what kind of concurrency problem that the COW approximation has. Can someone put the light on me?

For testing, I have hacked AbstractBuilder, StringBuilder and StringBuffer with the COW hack. And in a microbenchmark there is a significant boost in both cases, also NetBeans, Glassfish, my J2EE apps and so on worked great with this patch. Sorry but no deep multithreading testing...

Why not reintroducing it in JDK 7? No API changes, cheap change, and a good boost.

Salvo que tenga unos problemas rarísimos que mi corta mente no llega a alcanzar, no veo por qué no... al fin y al cabo StringBuilder es thread-unsafe de por si, y StringBuffer... bueno, si funcionaba el COW en versiones anteriores del JDK no veo la diferencia (a no ser que no fuera realmente thread-safe, claro :-)

Por espacio, no pego el código de AbstractBuilder, StringBuilder y StringBuffer que he tocado, es algo distinto al 1.4 pero el concepto es el mismo...

Hilos (para el que tenga interés en los detalles concretos, de momento podéis revisar ambos hilos): Actualización - 05/09/2008:
Parece más un problema de consumo de memoria que de sincronización. Entiendo perfectamente el tema (puede verse en la Bug Database, bugs 4259569, 4724129, 4524848, 4910256), pero es una auténtica pena por el acelerón que supondría. Así que en el foro he propuesto combinar esta opción con -XX:+AggressiveOpts. Seguremos informando...


lunes, 1 de septiembre de 2008

¿Funcionan las optimizaciones sobre el modelo de threading?

http://www.infoq.com/articles/java-threading-optimizations-p1

Interesantísimo artículo, que explica varias de las opciones de arranque de la JVM que yo recomendaba en el post JVM tuning: Parámetros de lanzamiento de JVM: Sun Hotspot, complementando así la información que publiqué en el post Allocation, deallocation and escape analysis for Java . Trata los siguientes conceptos:
  • Escape analysis - lock elision explained
  • Biased locking explained
  • Lock coarsening explained
  • Adaptive spinning explained
Adicionalmente presenta los resultados de un benchmarking hecho por él mismo probando combinaciones de varias de estas opciones y comparando el rendimiento de StringBuffer vs StringBuilder en ellas. Si la magia existiera, los tiempos del StringBuffer deberían tender a los del StringBuilder al aplicar todas las optimizaciones. Sus resultados:

Mis resultados en Linux x64 con 2xXEONx4Cores:

  • Son similares a estos en cuanto a los parámetros que más contribuyen al rendimiento y en cuanto a que "la magia no existe: usa versiones no sincronizadas de las clases!". Nota: las pruebas de rendimiento sobre varios núcleos y/o varios procesadores son muy distintas que en máquinas con un sólo núcleo, donde no hay contención entre threads "reales".
  • Curiosamente, en mis pruebas con distintas versiones del JDK (1.5, 1.6.0_07, 1.6.0_06-6p, y JRockit) he visto cómo el rendimiento en la "Performance Release 6" se ha incrementado drásticamente en el uso de StringBuilders frente a la edición "normal" de Java 6 (que por cieto es más lenta que JDK 1.5), pero no tanto de StringBuffers. Y también que los resultados de JRockit en ambos casos eran los mismos, es decir que JRockit sí que optimiza correctamente cuando no hay contención posible y Hotspot no del todo...

viernes, 29 de agosto de 2008

Es la guerra: IO vs "non blocking NIO"


En esta entrada anterior hice una pequeña y somera referencia hacia que los procesadores multicore y el uso de NPTL y otras librerías de threads optimizadas, pueden haber cambiado las reglas del juego. Y no estar tan claro que la E/S no bloqueante merezca la pena frente a una IO bloqueante "de toda la vida".

Parece que a lo largo de 2008 se ha armado un pequeño revuelo / revolución a este respecto. Como muestra estos dos botones: En definitiva, mi conclusión personal es la siguiente para máquinas de 8-32 cores:
  1. Servidores con poca concurrencia esperada: IO síncrona
  2. Servidores con concurrencia media esperada: IO síncrona
  3. Servidores con concurrencia alta esperada: depende, habría que probarlo
  4. Servidores con concurrencia brutal esperada: NIO asíncrona

En los escenarios 1 y 2, seguramente el problema de rendimiento y escalabilidad lo tengamos en otro sitio así que este no es un tema tan crítico en esos casos.

En cualquier caso, depende del uso y de lo que entendamos por poca o mucha concurrencia. El otro día un colega al que respeto un montón me comentaba que lo estamos viendo con un punto de vista exclusivamente de "contenidos de hipertexto"; en un entorno de servir contenidos multimedia el tiempo de cambio de contexto entre threads es inasumible de ninguna de las maneras.

domingo, 24 de agosto de 2008

Java SE 6u6-p Performance Release - YA DISPONIBLE EN OTRAS PLATAFORMAS

[Notas de octubre'08 y noviembre'08 ==>

Querido googler: si llegas a esta página -como muchos- buscando información de las performance releases del JRE 6, quizás te interesen los artículos posteriores 6u6-p: Pues en Linux x64 peta!!!!!!! y Identificación de bugs en la versión 6u6p y workaround ]


En la entrada Java SE 6u6p Performance Release remarqué que en julio estaba ya disponible la versión 6u6-p Performance Release, pero sólo lo estaba para Solaris 64 bits.

Pues bien, por fin ya está disponible para un número más que interesante de otras plataformas, y la sopresa es que no sólo de 64 bits sino también algunas de 32 bits:



Ala, a rellenar el cuestionario en http://java.sun.com/javase/performance/survey.html y descargar, a ver qué tal...

miércoles, 20 de agosto de 2008

Allocation, deallocation and escape analysis for Java

Enlazo con este interesante artículo en IBM Developer Works: Básicamente el anterior post trata dos temas muy interesantes desde el punto de vista teórico:
  • El mínimo coste de la creación de objetos y reserva de memoria en Java frente a C/C++. Esto es gracias a la arquitectura interna con intervención del GC.
  • La inclusión del Escape Analysis en Java 6 y cómo eso beneficia al alojamiento de objetos en el stack frente al heap, lo cual puede ser una signfiicativa mejora de rendimiento...
Pues bien, el segundo de ellos parece que todavía no es cierto en Java 6 (el artículo es de 2005). Básicamente, hasta donde yo sé, sí que está implementado el análisis de escape (con la opción -XX:+DoEscapeAnalysis) pero sólo se usa para eliminar bloqueos, no para el alojamiento en stack. Quizás en Java 7 la optimización sea completa...

En este hilo puede verse la misma conclusión: http://forums.java.net/jive/thread.jspa?threadID=22617&tstart=120. Sin embargo en este otro post de 2007 se asegura que el Escape Analysis se eliminó de Mustang, por lo que me surge la duda si en actualizaciones posteriores sí se hizo o si simplemente es que hay desinformación por ahí... ver: http://blog.nirav.name/2007/02/escape-analysis-in-mustang-ii.html.

Y en cualquier caso con los microbenchmarks que yo he hecho (Linux, kernel 2.6, 8 cores) no parece que se gane mucho en cuanto a eliminación de locks, sí que parece suponer una mínima mejora pero nada comparable con el biased locking. Con todo el miedo que tengo a los microbenchmarks, en cualquier caso lo cierto es que la opción UseBiasedLocking está activada por defecto en Java 6, mientras que DoEscapeAnalysis hay que activarla.

El sentido común me dice que la decisión de poner opciones por defecto (o no ponerlas) por algo será (si partimos de la premisa de que los ingenieros de Sun no son imbéciles profundos ;-) y por lo tanto seguramente es un análisis experimental e imperfecto, así que tendremos que esperar a Java 7 para que ambas líneas de aprovechamiento del "análisis de escape" funcionen adecuadamente.

P.D: en otro post comentaré acerca del peligro de los microbenchmarks y cómo deben hacerse para no obtener resultados equivocados (en teoría, aunque tampoco me fío del todo).

[Actualización: puede verse información más detallada sobre esta y otras opciones de optimizaciones sobre el modelo de Threading en el post ¿Funcionan las optimizaciones sobre el modelo de threading?]

[Actualización 06-09-2008: Definición de Escape analysis en la Wikipedia: http://en.wikipedia.org/wiki/Escape_analysis]

[Actualización 04-11-2008: Conceptos generales de rendimiento en Java, incluyendo estos nombrados: http://en.wikipedia.org/wiki/Java_performance]

viernes, 8 de agosto de 2008

JVM tuning: Parámetros de lanzamiento de JVM: Sun Hotspot

[Artículo actualizado el 3/11/2008 con información adicional sobre -XX:+DoEscapeAnalysis y -XX:+AggressiveOpts].

Parámetros de lanzamiento. JVM options. VM options. JDK option. Tuning parameter. Tunning.

O como queráis llamarlo. Por cierto que no se escribe Tunning, sino Tuning. a mi se me escapa mucho.

Sí, es tuning, pero no del de la foto :-)

La cuestión es de sobra comentada en este blog: cada vez el rendimiento sin tunear, o "Out-of-box performance" se aproxima más al mejor de los casos. Es decir en condiciones extremas puede haber una diferencia del 8% (que no es poco) pero puede ser un margen más que suficiente para no meterse en líos. Porque por toquitear podemos cagarla. Ahora o en un futuro. Pueden revisarse las reflexiones en la serie relacionada con la etiqueta Out-of-box.

A lo que vamos. Pero vale la pena intentarlo. Es más, casi diría que tenemos la obligación de intentarlo, cada aplicación es un mundo, los comportamientos de nuestros usuarios son otro mundo y las configuraciones y otros elementos para qué contarlo.

De todas formas, insisto en que no debe tunear por tunear, ni dejarse llevar por lo que supuesto expertos y teóricos aseguran en sus blogs (¿como este?). Las reglas de oro, en mi experiencia, son las siguientes:


  1. No te fíes de la teoría. O de los expertillos de salón. O de "tu intuición e inteligencia". Ni mucho menos de la de tu jefe. Prueba tú mismo la inclusión de opciones nuevas.
  2. No hagas Microbenchmarks. Mejor no hacerlos, puede llevar a conclusiones erróneas gracias a las optimizaciones (o no optimizaciones del Hotspot); aunque te creas más listo que nadie y pienses que lo estás simulando bien, lo más probable es que no sea así. Simula siempre con tu aplicación, no con un programita de esos que haces siempre para tomar tiempos.
  3. Paso 1: No tuning. Out-of-box. Déjalo como está (sin configurar o preconfigurado para tu servidor de aplicaciones), tocando únicamente los targets de memoria en cuanto a heap y revisando que aparezca el -server (por ejemplo en Glassfish V2, el comilador es -client por defecto). Si el rendimiento en el entorno de ejecución es adecuado, incluso en situaciones de carga, no lo toques. Si funciona no lo toques.
  4. Paso 2: Comienza a hacer pruebas de carga sobre tu sistema en pre-explotación. O en explotación. Pero pruebas de verdad, con un juego de pruebas coherente, con sus pausas aleatorias y la carga distribuida en distintos clientes con distintos anchos de banda simulado o real. Y ejecutado durante más de 2 horas cada uno. Lo mejor sería que las pruebas no fueran sintéticas 100% sino en base a los logs de ejecuciones pasadas, pausas, etc.
    1. Parte de la configuración base. Toma los tiempos de referencia.
    2. Sigue los pasos del apartado 4.2 del documento http://java.sun.com/performance/reference/whitepapers/tuning.html#section4.2. Empieza por el ejemplo 1. Aplica los parámetros y toma tiempos
    3. Repite el paso anterior con los ejemplo 2..7. Toma tiempos de todo ellos.
    4. Revisar las configuraciones de resultados publicados para los benchmarks del SPEC, y prueba con alguna de ellas (abajo pego un ejemplo).
    5. Por supuesto hay otras miles de opciones, muchas indocumentadas lamentablemente. Si tienes tiempo, ya sabes. ¡A probar!
    6. Toma tus propias conclusiones. Recuerda que un objetivo más importante que el rendimiento es la estabilidad al cabo del tiempo.
  5. Menos es más (esto también lo dicen los chicos de Usabilidad). Si dos alternativas dan resultados casi equivalentes, opta por la que menos opciones de lanzamiento tenga.
  6. Cada vez que cambies de versión de JDK, de versión de servidor de aplicaciones, de versión del kernel, o de hardware vuelve a hacer las comprobaciones, a no ser que en las notas de la versión dejen muy claro que sólo corrigen bugs que no te afectan. Apúntatelo en la cabecita. O en la libreta. O en la documentación. Un parámetro óptimo para una versión puede ser peor que la opción por defecto para la siguiente versión.
Dicho esto, y sabiendo que cada sistema y aplicación es un mundo, y los objetivos de negocio otro, voy a poner a continuación las opciones que a mi más mejor me funcionan.

  • Mi configuración HW y soft base, resumidamente es: máquinas x64, 2 procesadores XEON Quadcore (8 núcleos por máquina en total), 16 Gbytes de RAM, discos SAS en RAID 1, CentOS 5.1 kernel 2.6.18.
  • Viendo las configuraciones de resultados publicados en SPECjbb2005, una configuración que se repite con frecuencia es más o menos:
    • Para Hotspot: -Xms3650m -Xmx3650m -Xmn2000m -server -XX:+UseBiasedLocking -XX:+AggressiveOpts -XX:+UseParallelOldGC -Xss128k -XX:+UseLargePages -Xbatch
    • Para JRockit: -Xms3650m -Xmx3650m -Xns3000m -XXaggressive -XXlazyunlocking -Xlargepages -Xgc:genpar -XXtlasize:min=4k,preferred=1024k -XXcallprofiling
Lo que más me ha llamado la atención es que en casi ninguna configuración se abusa del tamaño de heap, aunque la JVM sea de 64bits en los tests no pasan de 3 GBytes de heap máximo. ¿Por qué? por tiempo de recolección de basura por supuesto. Esta puede ser una configuración válida para un benchmarking, pero en mi opinión es absurdo no aprovechar todo lo que nos da la máquina (o casi todo).

Los parámetros que mejores resultados me dan a mi en mi sistema y con mis circunstancias, y con estabilidad, son los siguientes, tanto en JDK 1.5 como en Java 6 (aunque en Java 6 algunas de ellas son valores ya por defecto): "-server -Xmx12288m -Xms12288m -XX:MaxPermSize=256m -XX:+UseParallelOldGC -XX:+UseAdaptiveSizePolicy -XX:+UseBiasedLocking -XX:+EliminateLocks -XX:+AggressiveOpts -Xverify:none -Djava.net.preferIPv4Stack=true"

De todo ello, lo más importante es elegir una buena estrategia de recolección de basura. En uno de los enlaces abajo están perfectamente explicadas todas las estrategias actuales, en mi caso el "stop-the-world" apenas se nota frente a una diferencia importante de rendimiento con estrategias concurrentes, quizás sea porque mi gráfico de objetos sea muy simple, un número medio de objetos con gran tamaño.

Un breve comentario de ellas:
  • -server: Entre otras diferencias con el modo cliente es el número de iteraciones antes de compilar una zona de código, 10000 frente a 1500. Se puede cambiar con la opción -XX:CompileThreshold, pero curiosamente mis pruebas han dado que un número ideal tiende a 7000-10000, indicando 0 o similar da resultados catastróficos... parece ser que por los inlinings y elecciones correctas, qué curioso. Nota: la opción -client puede ser la adecuada si lo que necesitas es buen tiempo de arranque o en situaciones en las que cambian mucho los JSP (cada JSP recompila a una nueva versión de la clase Servlet generada), pero algunas de las siguientes opciones no funcionarán.
  • -Xmx: tamaño máximo del heap, aún no tengo claro si incluye o no la zona de PermGen. Regla de oro: curiosamente la máquina donde se ejecutan JVM JAMÁS debe utilizar el swap a disco, la GC sería un desastre si tiene que ir moviendo páginas de un lado a otro.
  • -Xms: tamaño inicial del heap, en todos los sitios se recomienda igualarlo al máximo por rollos de fragmentación. Nota: esto no significa que el proceso consuma desde el principio esa cantidad de memoria física.
  • -XX:MaxPermSize: tamaño de la zona de generación permanente, donde nunca se hace GC (creo) ahí se alojan los objetos "clase" por ejemplo. Normalmente con 64 megas es más que suficiente pero si tienes muchos JSPs o muchos EJBs, mejor subirlo por si acaso (en Java CAPS por defecto es 192 megas).
  • -XX:+UseParallelOldGC: estrategia de máxima paralelización en la recolección de basura, no sólo usa múltples threads (por defecto tantos como threads reales máximos soporta tu máquina) en la recolección en las zonas jóvenes, sino también paraleliza la recolección en la zona antigua del heap. La opción por defecto con -server es UseParallelGC, en mi caso había una diferencia perceptible entre ambas. En muchos casos la opción adecuada debería ser -XX:+UseConcMarkSweepGC, en el que virtualmente no hay pausas perceptibles, pero el rendimiento global es menor. Por cierto, si vas al límite de consumo de heap o tienes algún memory leak, esta opción puede ser un desastre porque los tiempos de "stop de world" se hacen infinitos ya que no encuentra nada que liberar...
  • -XX:+UseAdaptiveSizePolicy: para no liarse con los tamaños de cada zona del heap, en mi caso lo mejor ha sido indicar este parámetro para que sea Hotspot quien vaya decidiendo y redimensionando por mi. Obviamente lo mejor es que no haya redimensiones y usar parámetros como -Xmn, -XX:NewRatio=2 y similares, pero hay que tener mucho cuidado porque pueden empezar a saltar OutOfMemory's cuando no deberían...
  • -XX:+AggressiveOpts: Aplica (si las hay) optimizaciones experimentales que se liberarán oficialmente en siguientes versiones del JDK.Habilita aquellas opciones de rendimiento que se prevén como habilitadas por defecto en siguientes releases del Hotspot, es decir muchas de las que explícitamente se especifican a continuación en realidad no es necesario indicarlas si aparece esta opción. Yo lo he usado siempre y nunca ha habido un comportamiento raro. En mi caso sí que aportaba un 1-2% de mejora sin afectar a la estabilidad, si no es el caso mejor no ponerlo por si acaso.
  • -XX:+UseBiasedLocking: Utiliza una estrategia de bloqueo que beneficia escenarios en los que sólo un thread pasa por una región de exclusión mutua (reduce la "contención") pero penaliza algunas estrategias de bloqueo. Teniendo en cuenta que la mitad del JDK está sincronizado y que no suele unsarse un String o un ByteArray desde dos threads concurrentes, es una gran idea. En Java 6 está habilitado por defecto, en JDK 1.5 hay que habilitarlo o especificar +AggresiveOpts.
  • -XX:+EliminateLocks: Otra técnica de optimización que se nota levemente, básicamente unifica regiones de exclusión mutua en un mismo thread. Una vez más el diseño de las clases del JDk hace que esta estrategia sea beneficiosa. Creo que no está habilitado por defecto en Java 6, salvo que especifiques +AggresiveOpts.
  • -Xverify:none: no verifica todas las clases contenidas en el bootclasspath y resto de cargas(ncluyendo el escaneo de todos los JARs) al arrancar. Acelera la fase de lanzamiento pero más vale ir con ojo porque está deshabilitando un control que mejor activar tras un par de semanas de funcionamiento. Cuidado por tanto con deshabilitar a la ligera la verificación si desplegamos código no confiable o de proveedores externos en nuestros servidores de aplicaciones, ya que esto puede derivar en alguna vulnerabilidad.
  • -Djava.net.preferIPv4Stack=true: Bueno, básicamente que no utilice el stack de IPv6 para el networking. Si no usas IPv6, activa esta opción y verás...
  • [-XX:+DoEscapeAnalysis: Esta es una opción que no está en JDK 1.5 y sí en Java 6 y JDK 7, en mis microbenckmarks daba peores resultados que desactivándolo curiosamente, aunque la teoría dice que debería funcionar para eliminar locks en regiones por las que sólo puede pasar un thread a la vez] NOTA DE REVISIÓN DE 3/11/2008: esta opción se habilita al establecer +AggresiveOpts, por lo que los microbenchmarks habría que revisarlos, porque los benchmars reales dan mejor resultado con +AggresiveOpts que sin ella.
  • [Otras opciones como -XX:+UseFastAccessorMethods y -XX:+StringCache no las he probado explícitamente. Al menos el primero de ellos creo que stá habilitado por defecto en todas las VM, y el segundo no lo tengo claro pero estoy casi convencido de que se ve afectado por +AggresiveOpts.]
Insisto, lo mejor es dejar el máximo número de opciones a su valor por defecto, aunque a todos nos gusta tunear. Y no te fies de los blogs. ¡Ni de este! Prueba, prueba y prueba

Ver algunas explicaciones y enlaces de interés en:
P.D: Empiezan los Juegos. Que gane el mejor y que nadie se ahogue entre la polución de Pekín...