java performance concurrency simpledateformat thread-local

java - ¿Cómo mejorar el rendimiento de SimpleDateFormat incluido en ThreadLocal?



performance concurrency (4)

Esto está en Java 7 (51) en RHEL con 24 núcleos. Estamos notando un aumento en los tiempos de respuesta promedio de un SimpleDateFormat envuelto en subproceso local a medida que aumentamos el tamaño del grupo de hilos. ¿Se espera esto? O, simplemente estoy haciendo algo estúpido?

Programa de prueba

public class DateFormatterLoadTest { private static final Logger LOG = Logger.getLogger(DateFormatterLoadTest .class); private final static int CONCURRENCY = 10; public static void main(String[] args) throws Exception { final AtomicLong total = new AtomicLong(0); ExecutorService es = Executors.newFixedThreadPool(CONCURRENCY); final CountDownLatch cdl = new CountDownLatch(CONCURRENCY); for (int i = 0; i < CONCURRENCY; i++) { es.execute(new Runnable() { @Override public void run() { try { int size = 65000; Date d = new Date(); long time = System.currentTimeMillis(); for (int i = 0; i < size; i++) { String sd = ISODateFormatter.convertDateToString(d); assert (sd != null); } total.addAndGet((System.currentTimeMillis() - time)); } catch (Throwable t) { t.printStackTrace(); } finally { cdl.countDown(); } } }); } cdl.await(); es.shutdown(); LOG.info("TOTAL TIME:" + total.get()); LOG.info("AVERAGE TIME:" + (total.get() / CONCURRENCY)); } }

Clase DateFormatter:

public class ISODateFormatter { private static final Logger LOG = Logger.getLogger(ISODateFormatter.class); private static ThreadLocal<DateFormat> dfWithTZ = new ThreadLocal<DateFormat>() { @Override public DateFormat get() { return super.get(); } @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd''T''HH:mm:ssZ", Locale.ENGLISH); } @Override public void remove() { super.remove(); } @Override public void set(DateFormat value) { super.set(value); } }; public static String convertDateToString(Date date) { if (date == null) { return null; } try { return dfWithTZ.get().format(date); } catch (Exception e) { LOG.error("!!! Error parsing dateString: " + date, e); return null; } } }

Alguien sugirió sacar el AtomicLong, así que solo quería compartir que no está jugando ningún papel en aumentar el tiempo promedio:

##NOT USING ATOMIC LONG## 2014-02-28 11:03:52,790 [pool-1-thread-1] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:328 2014-02-28 11:03:52,868 [pool-1-thread-6] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:406 2014-02-28 11:03:52,821 [pool-1-thread-2] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:359 2014-02-28 11:03:52,821 [pool-1-thread-8] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:359 2014-02-28 11:03:52,868 [pool-1-thread-4] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:406 2014-02-28 11:03:52,915 [pool-1-thread-5] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:453 2014-02-28 11:03:52,930 [pool-1-thread-7] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:468 2014-02-28 11:03:52,930 [pool-1-thread-3] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:468 2014-02-28 11:03:52,930 [main] INFO net.ahm.graph.DateFormatterLoadTest - CONCURRENCY:8 ##USING ATOMIC LONG## 2014-02-28 11:02:53,852 [main] INFO net.ahm.graph.DateFormatterLoadTest - TOTAL TIME:2726 2014-02-28 11:02:53,852 [main] INFO net.ahm.graph.DateFormatterLoadTest - CONCURRENCY:8 2014-02-28 11:02:53,852 [main] INFO net.ahm.graph.DateFormatterLoadTest - AVERAGE TIME:340 ##NOT USING ATOMIC LONG## 2014-02-28 11:06:57,980 [pool-1-thread-3] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:312 2014-02-28 11:06:58,339 [pool-1-thread-8] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:671 2014-02-28 11:06:58,339 [pool-1-thread-4] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:671 2014-02-28 11:06:58,307 [pool-1-thread-7] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:639 2014-02-28 11:06:58,261 [pool-1-thread-6] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:593 2014-02-28 11:06:58,105 [pool-1-thread-15] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:437 2014-02-28 11:06:58,089 [pool-1-thread-13] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:421 2014-02-28 11:06:58,073 [pool-1-thread-1] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:405 2014-02-28 11:06:58,073 [pool-1-thread-12] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:405 2014-02-28 11:06:58,042 [pool-1-thread-14] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:374 2014-02-28 11:06:57,995 [pool-1-thread-2] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:327 2014-02-28 11:06:57,995 [pool-1-thread-16] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:327 2014-02-28 11:06:58,385 [pool-1-thread-10] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:717 2014-02-28 11:06:58,385 [pool-1-thread-11] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:717 2014-02-28 11:06:58,417 [pool-1-thread-9] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:749 2014-02-28 11:06:58,418 [pool-1-thread-5] INFO net.ahm.graph.DateFormatterLoadTest - THREAD TIME:750 2014-02-28 11:06:58,418 [main] INFO net.ahm.graph.DateFormatterLoadTest - CONCURRENCY:16 ##USING ATOMIC LONG## 2014-02-28 11:07:57,510 [main] INFO net.ahm.graph.DateFormatterLoadTest - TOTAL TIME:9365 2014-02-28 11:07:57,510 [main] INFO net.ahm.graph.DateFormatterLoadTest - CONCURRENCY:16 2014-02-28 11:07:57,510 [main] INFO net.ahm.graph.DateFormatterLoadTest - AVERAGE TIME:585


SimpleDateFormat no seguro para subprocesos

Como indica la respuesta correcta de Martin Wilson , crear una instancia de SimpleDateFormat es relativamente costoso.

Sabiendo que su primer pensamiento podría ser: "Bueno, almacenemos en caché una instancia para su reutilización". Buen pensamiento, pero cuidado: la clase SimpleDateFormat no es segura para subprocesos . Así lo dice la documentación de la clase bajo su encabezado de sincronización .

Tiempo de joda

Una solución mejor es evitar las clases java.util.Date, .Calendar y SimpleDateFormat notoriamente molestas (y ahora están pasadas de moda). En su lugar, use cualquiera

  • Joda-Time
    biblioteca de código abierto de terceros, reemplazo popular para Fecha / Calendario.
  • paquete java.time
    Nuevo, incluido en Java 8 , que reemplaza las antiguas clases de Fecha / Calendario, inspiradas en Joda-Time, definidas por JSR 310.

Joda-Time está diseñado intencionalmente para ser seguro para la ejecución de subprocesos, en gran parte mediante el uso de objetos inmutables . Hay algunas clases mutables, pero esas no se usan usualmente.

Esta otra pregunta sobre explica que la clase DateTimeFormatter es segura para subprocesos. Por lo tanto, puede crear una instancia, almacenarla en caché y dejar que todos sus subprocesos utilicen ese formateador sin agregar sincronización adicional u otros controles de concurrencia.


Crear una instancia de SimpleDateFormat es muy costoso ( este artículo muestra algunos perfiles / puntos de referencia). Si esto es cierto, en comparación con el análisis de las fechas en cadenas, se sigue que a medida que aumenta el número de subprocesos (y, por lo tanto, el número de instancias de SimpleDateFormat, ya que son subprocesos), el tiempo promedio aumentará.


Nuestro caso de uso fue escribir una vez (solo hilo) y leer muchas veces (al mismo tiempo). Así que convertí Date to String en el momento de almacenar los datos, en lugar de hacerlo cada vez que se necesita responder una solicitud.


Otro enfoque para acelerar el formateo es almacenar en caché el resultado formateado. Esto considera el hecho de que generalmente no hay tantas fechas diferentes para formatear. Si divide el formato de fecha y hora, es incluso un mejor candidato para el almacenamiento en caché.

La desventaja de esto es que las implementaciones normales de la memoria caché de Java, como EHCache, son lentas, el acceso a la memoria caché solo toma más tiempo que el formato.

Hay otra implementación de caché alrededor que tiene tiempos de acceso a la par con un HashMap. En este caso obtienes una buena aceleración. Aquí encontrará mis pruebas de prueba de concepto: https://github.com/headissue/cache2k-benchmark/blob/master/zoo/src/test/java/org/cache2k/benchmark/DateFormattingBenchmark.java

Tal vez esto puede ser una solución dentro de su escenario.

Descargo de responsabilidad: estoy trabajando en cache2k ....