Hola Habr! Mi nombre es Artyom Dobrovinsky y soy desarrollador de Android en FINCH .
Una vez, envolviéndome en el humo de un cigarro matutino, estudié el código fuente de un ORM para Android. Al ver allí, un paquete llamado benchmarks
inmediatamente miró allí, y me sorprendió que todas las evaluaciones se hicieran usando Log.d(System.nanoTime())
. Esta no es la primera vez que veo esto. Para ser honesto, incluso vi puntos de referencia realizados con System.currentTimeMillis()
. El colapso de la conciencia de que algo debe cambiarse me obligó a dejar a un lado un vaso de whisky y sentarme al teclado.
¿Por qué está escrito este artículo?
La situación con la comprensión de cómo medir el rendimiento del código en Android es triste.
No hable de los perfiladores, pero en 2019 alguien sigue confiando en que la JVM hace todo lo que escribió el desarrollador y en el orden exacto en que se escribe el código. En realidad, no hay nada más lejos de la verdad.
De hecho, la desafortunada máquina virtual lucha contra un billón de lectores de botones descuidados que escriben su propio código, sin tener que preocuparse de cómo funcionará el procesador con todo esto. Esta batalla ha estado sucediendo durante varios años, y ella tiene un millón de optimizaciones difíciles en su manga que (si se ignoran) convertirán cualquier medición del rendimiento del programa en una pérdida de tiempo.
Es decir, los desarrolladores a veces no consideran necesario medir el rendimiento del código, y aún más a menudo no saben cómo. La dificultad radica en el hecho de que para realizar una evaluación del desempeño, es necesario crear las condiciones más similares e ideales para todos los casos; esta es la única forma de obtener información útil. Estas condiciones son creadas por soluciones no escritas en la rodilla.
Si necesita argumentos sobre si usar marcos de terceros para medir el rendimiento, siempre puede leer Alexei Shipilev y maravillarse con la profundidad del problema. Todo está en el artículo por referencia: por qué se necesita calentamiento antes de realizar el punto de referencia, por qué no se puede confiar en System.currentTimeMillis()
al contar el tiempo transcurrido, y bromas por 300. Excelente lectura.
¿Por qué puedo hablar de esto?
El hecho es que soy un desarrollador completamente desarrollado: no solo soy dueño del SDK de Android como si fuera mi proyecto favorito, sino que durante otro mes escribí el código para el backend.
Cuando traje mi primer microservicio a la revisión, y no había ninguna evaluación comparativa en README
, me miró con un malentendido. Recordé esto y nunca más volví a repetir este error. Porque se fue en una semana.
Vamos
¿Qué estamos midiendo?
Como parte del caso de las bases de datos de referencia para Android, decidí medir la velocidad de inicialización y la velocidad de escritura / lectura para ORM como Paper, Hawk, Realm y Room.
Sí, mido en una prueba NoSQL y una base de datos relacional: ¿cuál es la siguiente pregunta?
De lo que medimos
Parece que si estamos hablando de la JVM, entonces la elección es obvia: hay una JMH glorificada , perfecta e impecablemente documentada . Pero no, no inicia las pruebas de instrumentación para Android.
Google Calipher los sigue, con el mismo resultado.
Hay una bifurcación de Calipher llamada Spanner , que durante muchos años ha sido zeppercay y fomenta el uso de Androidx Benchmark .
Centrémonos en lo último. Si solo porque no teníamos otra opción.
Al igual que todo lo que se agregó a Jetpack y no se repensó al migrar desde la Biblioteca de soporte, Androidx Benchmark se ve y se comporta como si estuviera escrito en una semana y media como una tarea de prueba, y nadie más lo tocará. Además, esta biblioteca está un poco pasada, porque es más para evaluar las pruebas de IU. Pero por falta de lo mejor, puedes trabajar con ella. Esto nos salvará al menos de errores obvios y también ayudará con el calentamiento.
Para reducir la ridiculez de los resultados, haré todas las pruebas 10 veces y calcularé el promedio.
Dispositivo de prueba: Xiaomi A1. No es el más débil del mercado, Android "limpio".
Conectar una biblioteca a un proyecto
Hay excelentes instrucciones para conectar Andoridx Benchmark a un proyecto. Le recomiendo encarecidamente que no sea flojo y que conecte un módulo separado para realizar mediciones.
Progreso del experimento
Todos nuestros puntos de referencia se ejecutarán en el siguiente orden:
- Primero, iniciamos la base de datos en el cuerpo de prueba.
- Luego, en el bloque
benchmarkRule.scope.runWithTimingDisabled
, generamos datos que alimentamos a la base de datos. El código colocado en este circuito no se tendrá en cuenta en la evaluación. - En el mismo cierre agregamos la lógica de borrar la base de datos; asegúrese de que la base de datos esté vacía antes de escribir.
- La siguiente es la lógica de la escritura y la lectura. Asegúrese de inicializar la variable con el resultado de la lectura para que la JVM no elimine esta lógica del recuento de ejecución como no utilizada.
- Medimos el rendimiento de la inicialización de la base de datos en una función separada.
- Nos sentimos como un hombre de ciencia.
El código se puede encontrar aquí . Si tiene pereza para caminar, la función de medición para PaperDb se ve así:
@Test fun paperdbInsertReadTest() = benchmarkRule.measureRepeated {
Los puntos de referencia para el resto de ORM se parecen.
Resultados
Inicialización
El ganador es Realm, en segundo lugar es Paper. Lo que está haciendo Room, todavía puede imaginar que Hawk hace casi la misma cantidad de tiempo, es completamente incomprensible.
Escritura y lectura
Aquí nuevamente el ganador de Realm, pero en estos resultados huele a fracaso.
La diferencia de cuatro veces entre las dos bases de datos "más lentas" y las dieciséis veces entre las "más rápidas" y las "más lentas" es muy sospechosa. Incluso teniendo en cuenta el hecho de que la diferencia es estable.
Conclusión
Medir el rendimiento de su código es al menos por curiosidad. Incluso si hablamos de los casos más lanzados por la industria (como la evaluación de pruebas instrumentales para Android).
Hay muchas razones para usar marcos de terceros para este negocio (en lugar de escribir el suyo propio con el tiempo y las animadoras).
La situación en las bases de código es tal que todos están tratando de escribir en una arquitectura limpia, para la mayoría, el módulo con lógica de negocios es un módulo java, para conectar un módulo con JMH cercano y verificar el código para detectar cuellos de botella, funciona por un día. Y los beneficios, durante muchos años por venir.
¡Feliz codificación!
PD: Si un lector atento conoce el marco para realizar pruebas comparativas de pruebas instrumentales para Android, que no figuran en el artículo, por favor comparta en los comentarios.
PPS: el repositorio de prueba está abierto para solicitudes de extracción.