Fresco: aumento de rendimiento y solución de problemas de memoria: Deshágase de `Object.finalize ()`

Creado en 14 jul. 2016  ·  17Comentarios  ·  Fuente: facebook/fresco

Aquí (https://github.com/facebook/react-native/issues/8711) puede encontrar una explicación extensa de por qué Object.finalize() es malo, pero las dos razones principales por las que Fresco debería dejar de usarlo son:

  1. El FinalizerQueue puede llenarse y dejar de procesarse, esto puede suceder por muchas razones, y es posible que nunca se llame a Object.finalize() hasta que salga la JVM. Esto también significa que, _todos_ los objetos a los que se hace referencia desde un objeto cuyo finalizador no se llama, no se recolectan como basura. Por lo tanto, incluso los finalizadores que solo registran pueden provocar problemas de memoria.
  2. Los finalizadores ralentizan todo enormemente. Cita de java efectivo:

Ah, y una cosa más: existe una grave penalización de rendimiento por utilizar finalizadores. En mi máquina, el tiempo para crear y destruir un objeto simple es de aproximadamente 5,6 ns. Agregar un finalizador aumenta el tiempo a 2400 ns. En otras palabras, es aproximadamente 430 veces más lento crear y destruir objetos con finalizadores.

Sé que es tedioso publicar todo manualmente, pero hacerlo le dará a Fresco y a los proyectos dependientes un aumento extremo del rendimiento y una menor huella de memoria. Esto ayudará a todos.

Los finalizadores fueron simplemente una mala idea que nunca debería haber sucedido.

Comentario más útil

Esto parece ser un gran problema con Fresco. Acabamos de cambiar a Fresco de Picasso y ahora nuestro montón de memoria se ve así:

capture

No veíamos nada como esto antes en términos de la cantidad de finalizadores. Nuestros análisis muestran muchos OOM. No pretendo entender el problema tan bien como fabuloso, pero puedo verificar que el problema parece tener un impacto muy negativo.

Todos 17 comentarios

Un +1 enorme para esto. Nuestra aplicación usa Fresco extensamente, y he estado viendo muchas instancias FinalizerReference que no se recopilan y pueden llevar a un uso descontrolado de la memoria. ¡Tengo muchas ganas de ver esto hecho!

Si bien veo su punto de que tener finalizadores agrega una penalización marginal de rendimiento, no creo que sea la causa principal de los problemas de memoria aquí.
Fresco utiliza una gestión de memoria determinista, por lo que si se utiliza correctamente, toda la memoria se libera antes de llegar al finalizador.
Si no se usa correctamente (que al ser una biblioteca de código abierto no podemos garantizar), estamos usando los finalizadores para limpiar la memoria. La razón por la que estamos haciendo esto es porque estamos usando ashmem, que es una memoria compartida, por lo que si se aferra a la memoria, puede degradar no solo sus aplicaciones, sino también la experiencia de los teléfonos.

Vamos a echar otro vistazo a la penalización de rendimiento y la razón por la que ves tantas referencias de finalizador, pero realmente no creo que esa sea la causa del alto uso de memoria.

Tanto Facebook como Twitter están usando Fresco, por lo que si hubiera alguna regresión, lo veríamos en nuestros gráficos muy temprano y nos afectaría de manera importante.

@balazsbalazs Excepto en el caso de imágenes animadas, ¿verdad?

@balazsbalazs Y los finalizadores agregan más que una "penalización de rendimiento marginal". Son órdenes de magnitud. Solo la creación de instancias lleva 3 veces más tiempo (prueba aquí: https://github.com/facebook/react-native/issues/8780#issuecomment-233130403), si agrega los múltiples ciclos de GC que necesitan los objetos finalizables, obtendrá ralentizaciones mucho mayores (2400ns en lugar de 6ns aquí: http://www.informit.com/articles/article.aspx?p=1216151&seqNum=7).

Es simple: Fresco está desperdiciando recursos valiosos al usar una técnica obsoleta que se desaconseja desde hace años no por una, sino por 20 razones diferentes.

Lo que está haciendo es confundir su gestión de memoria con la de todas las demás bibliotecas que se ejecutan en la misma JVM.

También está desperdiciando años de optimización de GC.

@ fab1an :
Como dije antes: "Vamos a echar otro vistazo a la penalización de rendimiento y la razón por la que no se recopilan las referencias de Finalizer, pero realmente no creo que esa sea la causa del alto uso de memoria".

Como ocurre con todas las decisiones de ingeniería, aquí hay una compensación:

  • por un lado puedes ganar ciclos de CPU
  • por otro lado, corre el riesgo de perder una gran parte de la memoria compartida

La pregunta es cómo evalúa esta compensación: cuál es exactamente la penalización de rendimiento y cuánto puede perder si filtra un mapa de bits.

Ejemplo:
De acuerdo con sus medidas, la inicialización de un objeto toma 4.6us en lugar de 1.7us. Aunque eso es 3x, no es probable que tenga más de 100 objetos en una pantalla a la vez, lo que sigue siendo de 0,46 ms en lugar de 0,17 ms, lo que incluso en este caso extremo sería una pequeña parte del marco (16 ms), por lo que no resultaría en un cuadro saltado.

Por otro lado, si olvida cerrar una CloseableReference, lo que puede suceder muy fácilmente, ya que la gestión de memoria determinista es muy ajena a Java, puede perder unos pocos MB de memoria compartida de Android fácilmente. En los teléfonos de gama baja, la memoria es mucho más valiosa que los ciclos de la CPU.


Ahora bien, aquí el costo real es el GC para los finalizadores, y dado que Android no es Java, necesitamos medir la penalización tanto en Dalvik como en ART GC para comprender qué significa eso realmente para hacer esa compensación.

Deshacerse de los finalizadores introduce el riesgo de perder una gran cantidad de memoria, y dado que se trata de una biblioteca de código abierto y no podemos controlar las personas que llaman, la gestión de memoria determinista es algo inusual en Java, esto es un riesgo real.

@ fab1an : ya ha hecho un gran análisis sobre esto, me complacería que nos ayudara a analizar el costo de la penalización general para los finalizadores en Dalvik y ART. (Un poco más profundo que hay un GC de segundo paso y referencias de objetos en el montón).

Ah, y sí, tienes razón: tenemos que arreglar las imágenes animadas

@balazsbalazs Sé por qué estás usando finalizadores, he leído el código, por eso ya publiqué en https://github.com/facebook/react-native/issues/8711 :

"La finalización podría parecer ser un mecanismo bueno para asegurarse de que los recursos no llegan a salir no vendida, [..] un mecanismo de reserva alternativa, una 'salvaguardia segunda oportunidad', que automágicamente salvar el día disponer de los recursos que se olvidaron. Esta está completamente equivocado "

  1. Lo que se logra es un mecanismo que _a veces_ puede verificar si un usuario olvidó cerrar un recurso.
    Si su aplicación tiene suficiente HeapSize, pero no tiene memoria nativa, sus finalizadores no se ejecutarán para liberar recursos nativos ni advertirán a los usuarios y no hay nada que pueda hacer al respecto.
    Entonces, exactamente el caso sobre el que desea advertir es el caso en el que los finalizadores no podrán ayudarlo.
  2. Lo que logra el 100% del tiempo es una desaceleración de toda la máquina virtual en la que se ejecuta su código.

Como último comentario, cito Wikipedia (https://en.wikipedia.org/wiki/Finalizer#Problems)

"Los finalizadores pueden causar una cantidad significativa de problemas y, por lo tanto, varias autoridades los desaconsejan enérgicamente.

  • Es posible que no se llame a los finalizadores de manera oportuna, o en absoluto, por lo que no se puede confiar en que persistan en el estado, liberen recursos escasos o hagan cualquier otra cosa de importancia .
  • Los finalizadores se ejecutan en función de la recolección de basura, que generalmente se basa en la presión de la memoria administrada; no se ejecutan en caso de escasez de otros recursos y, por lo tanto, no son adecuados para administrar otros recursos escasos.
  • Los finalizadores lentos pueden retrasar a otros finalizadores.
  • Los finalizadores pueden causar problemas de sincronización, incluso en programas secuenciales (de un solo subproceso), ya que la finalización se puede realizar al mismo tiempo (concretamente, en uno o más programas separados
    hilos).
  • ...

No dedicaré más tiempo a perfilar lo que son esencialmente las condiciones de carrera que dependen del proveedor.

Considero que es una pérdida de tiempo de desarrollo reproducir errores que miles de personas han cometido y sobre el que han escrito antes.

Probablemente cambiaré a Picasso desde el cuadrado por ahora, ya que de todos modos tengo imágenes estáticas en su mayoría.

@balazsbalazs Y si analiza por qué la cola deja de procesarse, parece que hay más personas que tienen ese problema: https://stackoverflow.com/questions/37001181/android-finalizerdaemon-hanging-up

Informé un error con Google, pero no obtuve respuesta hasta ahora: https://code.google.com/p/android/issues/detail?id=215906

Este también es un problema en react-native-animatable con animaciones que se repiten:
https://github.com/oblador/react-native-animatable/issues/43

¿Alguna noticia sobre esto?

Esto parece ser un gran problema con Fresco. Acabamos de cambiar a Fresco de Picasso y ahora nuestro montón de memoria se ve así:

capture

No veíamos nada como esto antes en términos de la cantidad de finalizadores. Nuestros análisis muestran muchos OOM. No pretendo entender el problema tan bien como fabuloso, pero puedo verificar que el problema parece tener un impacto muy negativo.

@brianhama ¿Puedes publicar una captura de pantalla del montón de memoria usando picasso antes del cambio? Eso sería muy informativo y ayudaría con este problema.

@brianhama que es una máquina virtual creará un FinalizerReference para cada objeto que tenga un método finalize() . Lo que está viendo no es necesariamente la cola de objetos que esperan ser finalizados, sino simplemente la lista de todas las instancias de objetos que implementan finalize() . El tamaño retenido es obviamente enorme, ya que incluirá todas las imágenes que están en pantalla o en la caché de mapa de bits.

En relación con el problema de los finalizadores: hemos realizado un experimento durante un par de meses en el que proporcionamos una implementación separada de CloseableReference que no implementa finalize() , pero usa PhantomReference s para limpiar la memoria nativa. En los millones de dispositivos que han participado en el experimento, no hemos visto mejoras estadísticamente significativas en:

  • choques en general
  • fallas de memoria
  • rendimiento de desplazamiento
  • uso de CPU
  • drenaje de bateria

En conclusión, nos quedaremos con los finalizadores por ahora.

@foghina Genial que hayas tenido la

No hay muchos más datos que "ninguna de nuestras métricas cambió" :), y si los hubiera, dudo que pudiera publicarlos. Solo para aclarar, esto no quiere decir que los finalizadores sean buenos, sino que las opciones alternativas como PhantomReferences no son necesariamente mejores. Y no estamos en un lugar donde podamos vivir sin una solución para esto.

¿Fue útil esta página
0 / 5 - 0 calificaciones