Se mejoró considerablemente el entorno de ejecución de Android (ART) en la versión de Android 8.0. En la siguiente lista, se resumen las mejoras que los fabricantes de dispositivos pueden esperar en ART.
Recolector de elementos no utilizados de compactación simultánea
Como se anunció en Google I/O, ART incluye un nuevo recolector de basura (GC) de compactación simultánea en Android 8.0. Este recopilador compacta el heap cada vez que se ejecuta el GC y mientras se ejecuta la app, con solo una breve pausa para procesar las raíces del subproceso. Estos son sus beneficios:
- La GC siempre compacta el heap: tamaños de heap un 32% más pequeños en promedio en comparación con Android 7.0.
- La compactación permite la asignación de objetos de puntero de incremento local del subproceso: Las asignaciones son un 70% más rápidas que en Android 7.0.
- Ofrece tiempos de pausa un 85% más cortos para la comparativa de H2 en comparación con el GC de Android 7.0.
- Los tiempos de pausa ya no se ajustan según el tamaño del montón. Las apps deberían poder usar montones grandes sin preocuparse por la latencia.
- Detalles de implementación de GC: barreras de lectura:
- Las barreras de lectura son una pequeña cantidad de trabajo que se realiza para cada lectura de campo de objeto.
- Estos se optimizan en el compilador, pero podrían ralentizar algunos casos de uso.
Optimizaciones de bucles
ART emplea una amplia variedad de optimizaciones de bucles en la versión de Android 8.0:
- Eliminación de verificaciones de límites
- Estático: Se comprueba que los rangos se encuentran dentro de los límites en el tiempo de compilación.
- Dinámicas: Las pruebas de tiempo de ejecución garantizan que los bucles permanezcan dentro de los límites (de lo contrario, se anula la optimización).
- Eliminaciones de variables de inducción
- Cómo quitar la inducción sin carga
- Reemplazo de la inducción que se usa solo después del bucle por expresiones de forma cerrada
- Eliminación de código no alcanzado dentro del cuerpo del bucle y eliminación de bucles completos que se vuelven no alcanzados
- Reducción de la fuerza
- Transformaciones de bucles: inversión, intercambio, división, expansión, unimodular, etcétera
- SIMDización (también llamada vectorización)
El optimizador de bucles reside en su propio paso de optimización en el compilador de ART. La mayoría de las optimizaciones de bucles son similares a las optimizaciones y simplificaciones que se realizan en otros lugares. Algunos desafíos surgen con ciertas optimizaciones que reescriben el CFG de una manera más elaborada de lo habitual, ya que la mayoría de las utilidades de CFG (consulta nodes.h) se enfocan en compilar un CFG, no en reescribirlo.
Análisis de la jerarquía de clases
En Android 8.0, ART usa el análisis de jerarquía de clases (CHA), una optimización del compilador que desvirtualiza las llamadas virtuales en llamadas directas según la información generada por el análisis de las jerarquías de clases. Las llamadas virtuales son costosas, ya que se implementan alrededor de una búsqueda de vtable y requieren varias cargas dependientes. Además, las llamadas virtuales no se pueden insertar.
A continuación, se incluye un resumen de las mejoras relacionadas:
- Actualización dinámica del estado del método de implementación única: Al final del tiempo de vinculación de la clase, cuando se completó la tabla virtual, ART realiza una comparación entrada por entrada con la tabla virtual de la superclase.
- Optimización del compilador: El compilador aprovechará la información de implementación única de un método. Si un método A.foo tiene establecida la marca de implementación única, el compilador desvirtualizará la llamada virtual en una llamada directa y, además, intentará insertar la llamada directa como resultado.
- Invalidación del código compilado: También al final del tiempo de vinculación de la clase, cuando se actualiza la información de implementación única. Si el método A.foo que antes tenía una implementación única, pero ese estado ahora se invalida, todo el código compilado que depende de la suposición de que el método A.foo tiene una implementación única debe invalidar su código compilado.
- Anulación de la optimización: En el caso del código compilado en vivo que se encuentra en la pila, se iniciará la anulación de la optimización para forzar el código compilado invalidado al modo de intérprete y garantizar la corrección. Se usará un nuevo mecanismo de desoptimización que es un híbrido de desoptimización síncrona y asíncrona.
Cachés intercaladas en archivos .oat
Ahora, ART emplea cachés intercaladas y optimiza los sitios de llamadas para los que existen suficientes datos. La función de almacenamiento en caché intercalado registra información adicional del tiempo de ejecución en los perfiles y la usa para agregar optimizaciones dinámicas a la compilación anticipada.
Dexlayout
Dexlayout es una biblioteca que se introdujo en Android 8.0 para analizar archivos dex y reordenarlos según un perfil. Dexlayout tiene como objetivo usar la información de generación de perfiles en el tiempo de ejecución para reordenar las secciones del archivo dex durante la compilación de mantenimiento inactivo en el dispositivo. Al agrupar partes del archivo dex a las que se accede con frecuencia, los programas pueden tener mejores patrones de acceso a la memoria gracias a una mejor localidad, lo que ahorra RAM y acorta el tiempo de inicio.
Dado que la información del perfil solo está disponible después de que se ejecutan las apps, dexlayout se integra en la compilación en el dispositivo de dex2oat durante el mantenimiento en estado de inactividad.
Eliminación de la caché de Dex
Hasta Android 7.0, el objeto DexCache poseía cuatro arreglos grandes, proporcionales a la cantidad de ciertos elementos en el DexFile, a saber:
- cadenas (una referencia por DexFile::StringId)
- tipos (una referencia por DexFile::TypeId)
- métodos (un puntero nativo por DexFile::MethodId)
- campos (un puntero nativo por DexFile::FieldId)
Estos arrays se usaban para recuperar rápidamente los objetos que habíamos resuelto anteriormente. En Android 8.0, se quitaron todos los arrays, excepto el array de métodos.
Rendimiento del intérprete
El rendimiento del intérprete mejoró significativamente en la versión de Android 7.0 con la introducción de "mterp", un intérprete que incluye un mecanismo central de recuperación, decodificación e interpretación escrito en lenguaje ensamblador. Mterp se modela según el intérprete rápido de Dalvik y admite arm, arm64, x86, x86_64, mips y mips64. En el caso del código computacional, el mterp de ART es aproximadamente comparable al intérprete rápido de Dalvik. Sin embargo, en algunas situaciones, puede ser mucho más lento, incluso de forma drástica:
- Invoca el rendimiento.
- Manipulación de cadenas y otros usos intensivos de métodos reconocidos como intrínsecos en Dalvik.
- Mayor uso de memoria de pila
Android 8.0 aborda estos problemas.
Más inserciones
Desde Android 6.0, ART puede insertar cualquier llamada dentro de los mismos archivos dex, pero solo podía insertar métodos hoja de diferentes archivos dex. Esta limitación se debió a dos motivos:
- La inserción desde otro archivo dex requiere el uso de la caché de dex de ese otro archivo dex, a diferencia de la inserción del mismo archivo dex, que podría reutilizar la caché de dex del llamador. La caché de dex es necesaria en el código compilado para algunas instrucciones, como llamadas estáticas, carga de cadenas o carga de clases.
- Los mapas de pila solo codifican un índice de método dentro del archivo dex actual.
Para abordar estas limitaciones, Android 8.0 hace lo siguiente:
- Quita el acceso a la caché de dex del código compilado (consulta también la sección "Eliminación de la caché de dex").
- Extiende la codificación del mapa de pila.
Mejoras en la sincronización
El equipo de ART ajustó las rutas de código MonitorEnter/MonitorExit y redujo nuestra dependencia de las barreras de memoria tradicionales en ARMv8, reemplazándolas por instrucciones más nuevas (adquirir/liberar) siempre que fue posible.
Métodos nativos más rápidos
Las llamadas nativas más rápidas a la interfaz nativa de Java (JNI) están disponibles con las anotaciones @FastNative
y @CriticalNative
. Estas optimizaciones integradas del tiempo de ejecución de ART aceleran las transiciones de JNI y reemplazan la notación !bang JNI, que ahora está obsoleta. Las anotaciones no tienen efecto en los métodos no nativos y solo están disponibles para el código del lenguaje Java de la plataforma en bootclasspath
(sin actualizaciones de Play Store).
La anotación @FastNative
admite métodos no estáticos. Usa esto si un método accede a un jobject
como parámetro o valor que se muestra.
La anotación @CriticalNative
proporciona una forma aún más rápida de ejecutar métodos nativos, con las siguientes restricciones:
-
Los métodos deben ser estáticos, sin objetos para parámetros, valores de retorno o un
this
implícito. - Solo se pasan tipos primitivos al método nativo.
-
El método nativo no usa los parámetros
JNIEnv
yjclass
en la definición de su función. -
El método debe registrarse con
RegisterNatives
en lugar de depender de la vinculación dinámica de JNI.
@FastNative
puede mejorar el rendimiento del método nativo hasta 3 veces, y @CriticalNative
hasta 5 veces. Por ejemplo, una transición de JNI medida en un dispositivo Nexus 6P:
Invocación de la interfaz nativa de Java (JNI) | Tiempo de ejecución (en nanosegundos) |
---|---|
JNI normal | 115 |
!bang JNI | 60 |
@FastNative |
35 |
@CriticalNative |
25 |