Mejoras de ART en Android 8.0

El entorno de ejecución de Android (ART) se mejoró significativamente 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 montón cada vez que se ejecuta el GC y mientras se ejecuta la app, con solo una pausa breve para procesar las raíces de subprocesos. Estos son sus beneficios:

  • El GC siempre compacta el montón: un 32% más pequeño en promedio en comparación con Android 7.0.
  • La compactación habilita la asignación de objetos de puntero de aumento 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 pequeños para la comparativa de H2 en comparación con el GC de Android 7.0.
  • Los tiempos de pausa ya no se escalan con el tamaño del montón. Las apps deberían poder usar montones grandes sin preocuparse por el bloqueo.
  • Detalles de la implementación de GC: Barreras de lectura:
    • Las barreras de lectura son una pequeña cantidad de trabajo que se realiza para cada campo de objeto que se lee.
    • Se optimizan en el compilador, pero pueden 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 están dentro de los límites en el tiempo de compilación.
    • Dinámico: Las pruebas de tiempo de ejecución garantizan que los bucles se mantengan dentro de los límites (de lo contrario, se inhabilitarán).
  • Eliminaciones de variables de inducción
    • Quita la inducción muerta
    • Reemplaza 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, eliminación de bucles completos que se vuelven no alcanzados
  • Reducción de la intensidad
  • Transformaciones de bucles: reversión, intercambio, división, expansión, unimodular, etcétera
  • SIMDización (también llamada vectorización)

El optimizador de bucles reside en su propio pase de optimización en el compilador de ART. La mayoría de las optimizaciones de bucles son similares a las optimizaciones y simplificaciones de otros lugares. Los desafíos surgen con algunas optimizaciones que reescriben la 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 una CFG, no en reescribirla.

Análisis de jerarquía de clases

ART en Android 8.0 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 jerarquías de clases. Las llamadas virtuales son costosas, ya que se implementan alrededor de una búsqueda de vtable y toman un par de cargas dependientes. Además, las llamadas virtuales no se pueden intercalar.

Este es 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 clases, cuando se propaga la tabla v, ART realiza una comparación entrada por entrada con la tabla v 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á intercalar la llamada directa como resultado.
  • Invalidación del código compilado: También al final del tiempo de vinculación de clases, cuando se actualiza la información de implementación única, si el método A.foo que antes tenía 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 implementación única debe invalidar su código compilado.
  • Deoptimización: En el caso del código compilado en vivo que está en la pila, se iniciará la deoptimización para forzar el código compilado invalidado al modo de intérprete y garantizar la exactitud. Se usará un nuevo mecanismo de deoptimización que es un híbrido de la deoptimización síncrona y asíncrona.

Cachés intercalados en archivos .oat

ART ahora emplea cachés intercaladas y optimiza los sitios de llamada para los que existen datos suficientes. La función de cachés intercaladas registra información adicional del entorno 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. El objetivo de Dexlayout es usar la información de perfil de tiempo de ejecución para reordenar secciones del archivo dex durante la compilación de mantenimiento inactivo en el dispositivo. Cuando se agrupan partes del archivo dex a las que se accede con frecuencia, los programas pueden tener mejores patrones de acceso a la memoria a partir de una localidad mejorada, lo que ahorra RAM y acorta el tiempo de inicio.

Dado que, actualmente, 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 inactivo.

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)
  • (un puntero nativo por DexFile::MethodId).
  • (un puntero nativo por DexFile::FieldId).

Estos arrays se usaron para la recuperación rápida de objetos que resolvimos 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 con un mecanismo principal de recuperación, decodificación e interpretación escrito en lenguaje ensamblador. Mterp se basa en el intérprete rápido de Dalvik y admite arm, arm64, x86, x86_64, mips y mips64. En el caso del código de procesamiento, mterp de Art es aproximadamente comparable al intérprete rápido de Dalvik. Sin embargo, en algunas situaciones, puede ser mucho más lento:

  1. Rendimiento de la invocación
  2. La manipulación de cadenas y otros usuarios intensivos de métodos reconocidos como intrínsecos en Dalvik
  3. Mayor uso de memoria de pila.

Android 8.0 aborda estos problemas.

Más incorporación

A partir de Android 6.0, ART puede intercalar cualquier llamada dentro de los mismos archivos dex, pero solo podía intercalar métodos de hoja de diferentes archivos dex. Existen dos motivos para esta limitación:

  1. La incorporación desde otro archivo dex requiere usar la caché de dex de ese otro archivo dex, a diferencia de la incorporación del mismo archivo dex, que podría volver a usar 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.
  2. 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:

  1. Quita el acceso a la caché de dex del código compilado (consulta también la sección "Quita la caché de dex").
  2. Extiende la codificación del mapa de pila.

Mejoras en la sincronización

El equipo de ART ajustó las instrucciones de código de MonitorEnter/MonitorExit y redujo nuestra dependencia de las barreras de memoria tradicionales en ARMv8, reemplazándolas por instrucciones más nuevas (adquisición/liberación) 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 de 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 que se muestran ni un this implícito.
  • Solo se pasan tipos primitivos al método nativo.
  • El método nativo no usa los parámetros JNIEnv ni jclass en su definición de función.
  • El método debe registrarse con RegisterNatives en lugar de depender de la vinculación JNI dinámica.

@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