En los últimos días hemos tenido que optimizar el parseo de un archivo JSON de varios MB en Android. Evidentemente, hemos usado un pull-parser, pero además hemos tenido que hacer algún que otro hack para acelerar el procesamiento.

Una de las partes que más nos estaban tardando es el parseo de fechas, ya que dicho JSON constaba de unos 9000 elementos, cada uno con varias fechas, entre otros campos.

Para mostrarlo, hemos hecho un pequeño benchmark, que hemos ejecutado

En primer lugar, vamos a hacer la forma “no recomendada”, creando un nuevo DateFormat en cada iteración, cosa que jamás se debería hacer, porque la creación de este objeto es especialmente pesada.

for(int i=0;i<100000;++i) {
  DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
  df.setTimeZone(TimeZone.getTimeZone("UTC"));
  long date = df.parse("2014-01-01 12:34:56").getTime();
}

Utilizando este método, las cien mil iteraciones tardan en un Nexus 5 28674 milisegundos, mientras en un Galaxy mini, se ejecuta en 106601 ms.

Ahora pasemos al método “recomendado”, reutilizar el mismo objeto DateFormat para parsear todos los elementos.

DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
df.setTimeZone(TimeZone.getTimeZone("UTC"));
for(int i=0;i<100000;++i) {
  long date = df.parse("2014-01-01 12:34:56").getTime();
}

De esta forma, reducimos los tiempos a 16053 y 27445 ms, respectivamente en el Nexus 5 y el Galaxy mini.

En nuestro caso, estábamos usando este método, lo que nos retrasaba el proceso de fechas unos 3 segundos en el Nexus 5, intolerable. Por eso decidimos que, quizá, DateFormat es demasiado genérico, y una función ad-hoc sería mejor, dado que el formato de fechas sabemos que va a ser siempre el mismo. Por eso, escribimos la siguiente función:

private static long _parseDate(GregorianCalendar gc, String date) throws Exception {
  int year   = (date.charAt(0)-'0')*1000+(date.charAt(1)-'0')*100+(date.charAt(2)-'0')*10+(date.charAt(3)-'0');
  int month  = (date.charAt(5)-'0')*10+(date.charAt(6)-'0')-1;
  int day    = (date.charAt(8)-'0')*10+(date.charAt(9)-'0');
  int hour   = (date.charAt(11)-'0')*10+(date.charAt(12)-'0');
  int minute = (date.charAt(14)-'0')*10+(date.charAt(15)-'0');
  int second = (date.charAt(17)-'0')*10+(date.charAt(18)-'0');
  gc.set(year, month, day, hour, minute, second);

  return gc.getTimeInMillis();
}

Como véis, no es muy elegante, pero hace lo que debe hacer. De nuevo, volvemos a ejecutar las cien mil iteraciones llamando a esta función.

GregorianCalendar gc = new GregorianCalendar();
gc.setTimeZone(TimeZone.getTimeZone("UTC"));
gc.clear();
for(int i=0;i<100000;++i) {
  long date = _parseDate(gc, "2014-01-01 12:34:56");
}

Utilizando este método, el tiempo en el Nexus 5 baja hasta unos impresionantes 266 ms, mientras que en el Galaxy mini, se ejecuta en 801 ms.

Búsqueda en un array