Mejoras de Android 8.0 ART

El tiempo de ejecución de Android (ART) se ha mejorado significativamente en la versión de Android 8.0. La siguiente lista resume las mejoras que los fabricantes de dispositivos pueden esperar en ART.

Recolector de basura de compactación concurrente

Como se anunció en Google I/O, ART presenta un nuevo recolector de basura (GC) de compactación simultánea en Android 8.0. Este recopilador compacta el montón cada vez que se ejecuta GC y mientras se ejecuta la aplicación, con solo una breve pausa para procesar las raíces de subprocesos. Aquí están sus beneficios:

  • GC siempre compacta el montón: un 32 % de tamaño de montón más pequeño en promedio en comparación con Android 7.0.
  • La compactación permite la asignación de objetos de puntero de relieve local de subprocesos: las asignaciones son un 70 % más rápidas que en Android 7.0.
  • Ofrece tiempos de pausa un 85 % más pequeños para el punto de referencia H2 en comparación con Android 7.0 GC.
  • Los tiempos de pausa ya no se escalan con el tamaño del almacenamiento dinámico; las aplicaciones deberían poder usar montones grandes sin preocuparse por los bloqueos.
  • Detalle de implementación de GC - Barreras de lectura:
    • Las barreras de lectura son una pequeña cantidad de trabajo realizado para cada campo de objeto leído.
    • Estos están optimizados en el compilador, pero pueden ralentizar algunos casos de uso.

Optimizaciones de bucle

ART emplea una amplia variedad de optimizaciones de bucle en la versión de Android 8.0:

  • Eliminaciones de verificación de límites
    • Estático: se ha demostrado que los rangos están dentro de los límites en tiempo de compilación
    • Dinámico: las pruebas en tiempo de ejecución aseguran que los bucles se mantengan dentro de los límites (de lo contrario, desactívelos)
  • Eliminaciones de variables de inducción
    • Eliminar la inducción muerta
    • Reemplace la inducción que se usa solo después del ciclo por expresiones de forma cerrada
  • Eliminación de código muerto dentro del cuerpo del bucle, eliminación de bucles completos que se vuelven muertos
  • Reducción de fuerza
  • Transformaciones de bucle: inversión, intercambio, división, desenrollado, unimodular, etc.
  • SIMDización (también llamada vectorización)

El optimizador de bucle reside en su propio pase de optimización en el compilador ART. La mayoría de las optimizaciones de bucle son similares a las optimizaciones y simplificaciones en otros lugares. Surgen desafíos con algunas optimizaciones que reescriben el CFG de una manera más elaborada de lo habitual, porque la mayoría de las utilidades de CFG (ver nodes.h) se enfocan en construir un CFG, no en reescribir uno.

Análisis de jerarquía de clases

ART en Android 8.0 usa Class Hierarchy Analysis (CHA), una optimización del compilador que desvirtualiza las llamadas virtuales en llamadas directas según la información generada al analizar las jerarquías de clases. Las llamadas virtuales son caras, ya que se implementan en torno a una búsqueda de vtable y requieren un par de cargas dependientes. Además, las llamadas virtuales no se pueden incorporar.

Aquí hay 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 ha completado la vtable, ART realiza una comparación entrada por entrada con la vtable 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 establecido un indicador de implementación única, el compilador desvirtualizará la llamada virtual en una llamada directa y, como resultado, intentará incorporar la llamada directa.
  • 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 anteriormente tenía una implementación única pero ese estado ahora está invalidado, todo el código compilado depende de la suposición de que el método A. foo tiene necesidades de implementación única para invalidar su código compilado.
  • Desoptimización: para el código compilado en vivo que está en la pila, se iniciará la desoptimización para forzar el modo de interpretación del código compilado invalidado para garantizar la corrección. Se utilizará un nuevo mecanismo de desoptimización que es un híbrido de desoptimización síncrona y asíncrona.

Cachés en línea en archivos .oat

ART ahora emplea cachés en línea y optimiza los sitios de llamadas para los que existen suficientes datos. La función de cachés en línea registra información de tiempo de ejecución adicional en perfiles y la usa para agregar optimizaciones dinámicas a la compilación anticipada.

Dexlayout

Dexlayout es una biblioteca introducida en Android 8.0 para analizar archivos dex y reordenarlos según un perfil. Dexlayout tiene como objetivo utilizar la información de creación de perfiles en tiempo de ejecución para reordenar secciones del archivo dex durante la compilación de mantenimiento inactivo en el dispositivo. Al agrupar partes del archivo dex a las que a menudo se accede juntas, los programas pueden tener mejores patrones de acceso a la memoria desde una localidad mejorada, ahorrando RAM y acortando el tiempo de inicio.

Dado que la información del perfil actualmente está disponible solo después de que se hayan ejecutado las aplicaciones, dexlayout se integra en la compilación en el dispositivo de dex2oat durante el mantenimiento inactivo.

Eliminación de caché de Dex

Hasta Android 7.0, el objeto DexCache poseía cuatro matrices grandes, proporcionales a la cantidad de ciertos elementos en 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).

Estas matrices se utilizaron para la recuperación rápida de objetos que resolvimos previamente. En Android 8.0, se eliminaron todas las matrices excepto la matriz de métodos.

Actuación 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 presenta un mecanismo básico de obtención/descodificación/interpretación escrito en lenguaje ensamblador. Mterp sigue el modelo del intérprete rápido de Dalvik y es compatible con arm, arm64, x86, x86_64, mips y mips64. Para el código computacional, el mterp de Art es más o menos comparable al intérprete rápido de Dalvik. Sin embargo, en algunas situaciones puede ser significativamente, e incluso dramáticamente, más lento:

  1. Invocar rendimiento.
  2. Manipulación de cadenas y otros grandes usuarios de métodos reconocidos como intrínsecos en Dalvik.
  3. Mayor uso de memoria de pila.

Android 8.0 soluciona estos problemas.

más en línea

Desde Android 6.0, ART puede incorporar cualquier llamada dentro de los mismos archivos dex, pero solo puede incorporar métodos de hojas de diferentes archivos dex. Había dos razones para esta limitación:

  1. La inserción de otro archivo dex requiere el uso de la caché dex de ese otro archivo dex, a diferencia de la inserción del mismo archivo dex, que podría simplemente reutilizar la caché dex de la persona que llama. La memoria caché dex se necesita en el código compilado para un par de instrucciones, como llamadas estáticas, carga de cadenas o carga de clases.
  2. Los mapas de pila solo codifican un índice de método dentro del archivo dex actual.

Para abordar estas limitaciones, Android 8.0:

  1. Elimina el acceso a la memoria caché dex del código compilado (consulte también la sección "Eliminación de la memoria caché dex")
  2. Amplía la codificación del mapa de pila.

Mejoras de 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 con instrucciones más nuevas (adquirir/liberar) donde 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 mediante las anotaciones @FastNative y @CriticalNative . Estas optimizaciones de tiempo de ejecución de ART incorporadas aceleran las transiciones JNI y reemplazan la notación !bang JNI ahora 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. Use esto si un método accede a un jobject como parámetro o valor de retorno.

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 devueltos o un this implícito.
  • Solo los tipos primitivos se pasan al método nativo.
  • El método nativo no utiliza los parámetros JNIEnv y jclass en la definición de su función.
  • El método debe estar registrado con RegisterNatives en lugar de confiar en 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 JNI medida en un dispositivo Nexus 6P:

Invocación de la interfaz nativa de Java (JNI) Tiempo de ejecución (en nanosegundos)
JNI regular 115
!explosión JNI 60
@FastNative 35
@CriticalNative 25