Saltar al contenido principal

Rendimiento de React Native en Marketplace

· 6 min de lectura
Ingeniero de Software en Facebook
Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

React Native se utiliza en múltiples lugares de diversas aplicaciones de la familia Facebook, incluida una pestaña principal en las aplicaciones principales de Facebook. El enfoque de este artículo es un producto altamente visible: Marketplace. Está disponible en una docena de países y permite a los usuarios descubrir productos y servicios ofrecidos por otros usuarios.

En el primer semestre de 2017, mediante el esfuerzo conjunto del equipo Relay, el equipo Marketplace, el equipo de Plataforma JS Móvil y el equipo React Native, redujimos a la mitad el Tiempo hasta la Interacción (TTI) de Marketplace en dispositivos Android de Year Class 2010-11. Facebook históricamente ha considerado estos dispositivos como Android de gama baja, y presentan los TTIs más lentos en cualquier plataforma o tipo de dispositivo.

Un inicio típico de React Native se ve así:

Aclaración: las proporciones no son representativas y variarán según cómo se configure y utilice React Native.

Primero inicializamos el núcleo de React Native (también llamado "Bridge") antes de ejecutar el JavaScript específico del producto, que determina qué vistas nativas renderizará React Native durante el Tiempo de Procesamiento Nativo.

Un enfoque diferente

Uno de nuestros primeros errores fue permitir que Systrace y CTScan guiaran nuestros esfuerzos de rendimiento. Estas herramientas nos ayudaron a encontrar muchas mejoras fáciles en 2016, pero descubrimos que tanto Systrace como CTScan no representan escenarios de producción reales y no pueden emular lo que ocurre en entornos reales. Las proporciones de tiempo en los desgloses suelen ser incorrectas y, a veces, completamente desacertadas. En casos extremos, algunas operaciones que esperábamos tomaran milisegundos tardaban cientos o miles de milisegundos. Dicho esto, CTScan es útil y hemos comprobado que detecta un tercio de las regresiones antes de que lleguen a producción.

En Android, atribuimos las limitaciones de estas herramientas a tres factores: 1) React Native es un framework multihilo, 2) Marketplace coexiste con múltiples vistas complejas como Newsfeed y otras pestañas principales, y 3) los tiempos de cálculo varían enormemente. Por ello, este semestre dejamos que las mediciones y desgloses de producción guiaran casi todas nuestras decisiones y priorizaciones.

Siguiendo el camino de la instrumentación en producción

Instrumentar la producción puede parecer simple superficialmente, pero resultó ser un proceso bastante complejo. Tomó múltiples ciclos de iteración de 2-3 semanas cada uno; debido a la latencia desde integrar un commit en master, hasta publicar la app en Play Store, y recopilar suficientes muestras de producción para validar nuestro trabajo. Cada ciclo implicaba verificar si nuestros desgloses eran precisos, si tenían el nivel adecuado de granularidad y si sumaban correctamente al tiempo total. No podíamos confiar en versiones alpha y beta porque no representan a la población general. En esencia, construimos meticulosamente un rastreo de producción muy preciso basado en millones de muestras agregadas.

Una razón por la que verificamos meticulosamente que cada milisegundo en los desgloses sumara correctamente a sus métricas padre fue que descubrimos vacíos en nuestra instrumentación. Resultó que nuestros desgloses iniciales no contabilizaban las pausas causadas por saltos entre hilos. Los saltos de hilo en sí no son costosos, pero saltar a hilos ocupados que ya están trabajando es muy costoso. Finalmente reproducimos estos bloqueos localmente agregando llamadas Thread.sleep() en momentos clave, y los solucionamos mediante:

  1. eliminar nuestra dependencia de AsyncTask,

  2. revertir la inicialización forzada de ReactContext y NativeModules en el hilo UI, y

  3. eliminar la dependencia de medir ReactRootView durante la inicialización.

Juntos, la eliminación de estos problemas de bloqueo de hilos redujo el tiempo de inicio en más de un 25%.

Las métricas de producción también desafiaron algunas de nuestras suposiciones anteriores. Por ejemplo, solíamos precargar muchos módulos de JavaScript en la ruta de inicio bajo el supuesto de que ubicar módulos juntos en un único paquete reduciría su costo de inicialización. Sin embargo, el costo de precargar y colocar estos módulos superó ampliamente los beneficios. Al reconfigurar nuestras listas negras de requerimientos en línea y eliminar módulos de JavaScript de la ruta de inicio, pudimos evitar cargar módulos innecesarios como Relay Classic (cuando solo Relay Modern era necesario). Hoy, nuestro desglose RUN_JS_BUNDLE es más de un 75% más rápido.

También encontramos mejoras al investigar módulos nativos específicos del producto. Por ejemplo, al inyectar de manera diferida las dependencias de un módulo nativo, redujimos el costo de ese módulo nativo en un 98%. Al eliminar la contención del inicio de Marketplace con otros productos, reducimos el inicio en un intervalo equivalente.

Lo mejor es que muchas de estas mejoras son ampliamente aplicables a todas las pantallas construidas con React Native.

Conclusión

La gente asume que los problemas de rendimiento en el inicio de React Native son causados por JavaScript lento o tiempos de red excesivamente altos. Si bien acelerar cosas como JavaScript reduciría el TTI en una cantidad no trivial, cada uno de estos factores contribuye con un porcentaje mucho menor del TTI de lo que se creía anteriormente.

La lección hasta ahora ha sido: ¡mide, mide, mide! Algunas mejoras provienen de trasladar costos de tiempo de ejecución al tiempo de compilación, como Relay Modern y los Módulos Nativos Diferidos. Otras mejoras provienen de evitar trabajo al ser más inteligentes sobre la paralelización de código o eliminar código muerto. Y algunas mejoras provienen de grandes cambios arquitectónicos en React Native, como limpiar bloqueos de hilos. No existe una solución grandiosa para el rendimiento, y las mejoras de rendimiento a largo plazo provendrán de instrumentación incremental y optimizaciones. No permitas que el sesgo cognitivo influya en tus decisiones. En su lugar, recopila e interpreta cuidadosamente datos de producción para guiar el trabajo futuro.

Planes futuros

A largo plazo, queremos que el TTI de Marketplace sea comparable al de productos similares construidos nativamente y, en general, que el rendimiento de React Native esté a la par del rendimiento nativo. Además, aunque este semestre redujimos drásticamente el costo de inicio del puente en aproximadamente un 80%, planeamos llevar el costo del puente de React Native cerca de cero mediante proyectos como Prepack y más procesamiento en tiempo de compilación.