jueves, 11 de septiembre de 2014

Problemas de frame-rate en Android por bucles for-each para ArrayList

Hablamos de Java, por supuesto. El uso de los bucles for-each en Android al recorrer colecciones ArrayList, fue un terrible quebradero de cabeza en el desarrollo de Boo & Marung que incluso provocó problemas de diseño. Mucha gente tiene dificultades para mantener estable la tasa de frames y nunca imagina que se debe a este asunto.

Un bucle for-each es un tipo de bucle para recorrer colecciones que es muy recomendable en las buenas prácticas de programación:
ArrayList<Enemigo> listaDeEnemigos;
...
for (Enemigo enemigo : ListaDeEnemigos) {
    enemigo.pintar()
    ...
}
El problema de este tipo de bucles para recorrer listas ArrayList en Android es que, al menos en las máquinas virtuales y versiones en las que yo lo he probado, ocasiona graves problemas de rendimiento si ejecutamos estos bucles frecuentemente.

Google reconoce en voz baja y parcialmente este problema:
With an ArrayList, a hand-written counted loop is about 3x faster (...) consider a hand-written counted loop for performance-critical ArrayList iteration.
Fuente: http://developer.android.com/training/articles/perf-tips.html#Loops

Pero Google se queda corto. El estropicio en aplicaciones en tiempo real es mayúsculo si se usan mucho estos bucles porque el consumo de memoria es tal, que el recolector de basura se dispara, provocando importantes problemas de rendimiento.

No me consta que esto ocurra en otras plataformas, sino solo en Android, por lo que no hay motivo para no usar este tipo de bucles en ellas; al contrario, es lo deseable.

El caso es que este asunto provocó en mi juego problemas de diseño porque ante la falta de estabilidad de los frames en los primeros prototipos, no pensé que se debía a este tipo de bucles y eso me hizo probar métodos cada vez más rebuscados de mejora del rendimiento.

Pero, ¿por qué pasa esto cuando se trata de ArrayList en Android? Ni idea. Son cuestiones de implementación de la máquina virtual por parte de Google que, espero, se solucionarán algún día, si es que no lo están ya.

Así pues, si tienes problemas en tus juegos en Android con la tasa de frames, y no sabes qué más hacer para mejorarla, fíjate si utilizas bucles for-each para recorrer colecciones ArrayList (con otras colecciones en teoría no hay problema). Si es así, prueba a cambiarlos por bucles clásicos:
ArrayList<Enemigo> listaDeEnemigos;
...
for (int i = 0, n = listaDeEnemigos.size(); i < n; i++) {
    Enemigo enemigo = listaDeEnemigos.get(i);
    enemigo.pintar()
    ...
}
Otros métodos para mejorar el rendimiento incluyen, por ejemplo, evitar crear y liberar objetos durante el juego en sí, dejando estas tareas para los momentos de carga de niveles. O utilizar un patrón de diseño Flyweight para aliviar el uso de memoria. Técnicas que, afortunadamente, sí se mueven en el ámbito de la lógica.

No hay comentarios:

Publicar un comentario