domingo, diciembre 26, 2010

Escribiendo micro-benchmarks con jmicrobench

domingo, diciembre 26, 2010 por Martín

Tal y como escribía hace unos días, mi opinión es que los microbenchmarks no son una técnica recomendada para sacar conclusiones sobre el rendimiento global de nuestros programas.

Sin embargo sí que existe una serie de circunstancias en las que es importante el conocer el rendimiento de tu lógica de negocio. Por ejemplo cuando estamos trabajando en sistemas que requieren procesar una gran cantidad de transacciones por segundo. Imaginaros quizás un sistema de pagos por Internet, o el software de una casa de apuestas, o la línea de almacén de un gran fabricante de lo que sea.

En estos casos no sólo es necesario el tener la confianza en que el software va a soportar el volumen esperado sino que es necesario garantizarlo. ¿Cómo lo podemos garantizar? Con tests. Pero hacer los tests puede ser complicado. Por eso se hacen útiles frameworks para la creación de microbenchmarks como jmicrobench del que nunca había oido hablar pero que en breve os contaré en otro post como he llegado a el.

jmicrobench es una extensión de jUnit que define un nuevo ejecutor de Tests: PerformanceTestRunner que se encarga de ejecutar nuestros tests y de calcular el número de operaciones por segundo que ejecuta, su media, e incluso de generar gráficas. Un test de rendimiento se crearía de la siguiente forma:


@RunWith(PerformanceTestRunner.class)
@PerformanceTest(
projectName="Test",
warmupPasses=3,
maxPasses=100,
stablePasses=0,
runsToAverage=3,
durationMillis=1000,
warmupDurationMillis=300)
public class BankProcessorPerformanceTest {


En este código básicamente le estamos diciendo al framework que ejecute nuestro test durante un segundo, que haga tres pasadas de calentamiento (para calentar el JIT), que como mucho lo ejecute 100 veces, y que se quede con una media de tres pases descartando los altos y bajos. La ejecución es como un test de jUnit normal, pero con la diferencia de que al terminar el test se crean ficheros con las estadísticas de las diferentes ejecuciones.

Para probar jmicrobench decidí crear un mini proyecto con la típica clase que realiza una transferencia de dinero entre dos cuentas. Y creé tres versiones diferentes de un método para transferir dinero:



public void processTransaction(BankAccount origin,
BankAccount destination,
int amount) {

origin.setMoney(origin.getMoney()-amount);
destination.setMoney(destination.getMoney()+amount);
}


public void processSynchronizedTransaction(BankAccount origin,
BankAccount destination,
int amount) {

synchronized(this) {
origin.setMoney(origin.getMoney()-amount);
destination.setMoney(destination.getMoney()+amount);
}
}


public void processSemaphoreTransaction(BankAccount origin,
BankAccount destination,
int amount) throws InterruptedException {

available.acquire();
origin.setMoney(origin.getMoney()-amount);
destination.setMoney(destination.getMoney()+amount);
available.release();
}


Como veis son tres formas diferentes. La primera transfiere el dinero sin utilizar ningún tipo de bloqueos, la segunda utiliza un bloque synchronized y la tercera un semáforo. Entre la segunda y la tercera opción no hay realmente ninguna diferencia, simplemente el artefacto utilizado para el bloqueo. Está hecho así simplemente para probar el framework.

Una vez creada la clase, preparamos los tests. Pongo un por brevedad ya que lo único que cambia realmente son las llamadas a los métodos:

@Test
@PerformanceTest(groupName="Pruebas de rendimiento 1")
public void testPerformance() {

for (int i=0;i<1000;i++) {
bankProcessor.processTransaction(origin, destination, 100);
}
new PerformanceTestController().addNumberOfOperations(1000);
}

Se ejecuta jUnit y jmicrobench nos da los resultados:


#Pruebas de rendimiento 1.properties ...
opsPerSecond=189814013,922643
operations=189814333,000000
duration_nanos=1000001681,000000
YVALUE=189814013,922643

#Pruebas de rendimiento 2.properties ...
opsPerSecond=86023026,327911
operations=86023333,000000
duration_nanos=1000003565,000000
YVALUE=86023026,327911

#Pruebas de rendimiento 3.properties ...
opsPerSecond=2671298,505522
operations=2671333,000000
duration_nanos=1000012913,000000
YVALUE=2671298,505522

Los resultados eran predecibles. El primer método ejecuta una nada desdeñable cantidad de 189 millones de transacciones por segundo, el segundo que es código con bloqueos se va a las 86M TPS mientras que el tercero que utiliza un mecanismo más complejo de sincronización (en este caso sin ventajas) cae a los 2,6 millones de TPS.

Como podéis apreciar, el framework se hace muy útil para el testeo de este tipo de lógica de negocio. Tened en cuenta que esto no pretende ser un JMeter y no sirve para testeo end-to-end, ni por el contrario JMeter sirve para esto.

Pues nada, espero que os sea útil esta herramienta más.

comments

2 Respuestas a "Escribiendo micro-benchmarks con jmicrobench"
jcesarperez dijo...
18:20

Muy útil Martin, gracias!
Tenía pendiente ver la presentación de InfoQ de LMAX, pero si vas a hacer un resumen me espero!

Féliz Navidad!


Martín dijo...
20:22

jejjee, pues me has cazado, esa presentación ya me la vi y tengo escrito un resumen bastante largo.

Estoy aprovechando algunos temas que han salido para unos posts preliminares.