Tengo una aplicación Javascript bastante compleja, que tiene un bucle principal que se llama 60 veces por segundo. Parece que hay una gran cantidad de recolección de basura (según la salida de 'diente de sierra' de la línea de tiempo de la memoria en las herramientas de desarrollo de Chrome), y esto a menudo afecta el rendimiento de la aplicación.
Entonces, estoy tratando de investigar las mejores prácticas para reducir la cantidad de trabajo que tiene que hacer el recolector de basura. (La mayor parte de la información que he podido encontrar en la web se refiere a evitar fugas de memoria, lo cual es una cuestión ligeramente diferente: mi memoria se está liberando, es solo que hay demasiada recolección de basura). que esto se reduce principalmente a reutilizar objetos tanto como sea posible, pero por supuesto, el diablo está en los detalles.
La aplicación está estructurada en 'clases' siguiendo las líneas de Herencia de JavaScript simple de John Resig .
Creo que un problema es que algunas funciones se pueden llamar miles de veces por segundo (ya que se usan cientos de veces durante cada iteración del bucle principal) y quizás las variables de trabajo locales en estas funciones (cadenas, matrices, etc.) podría ser el problema.
Soy consciente de la agrupación de objetos para objetos más grandes / pesados (y usamos esto hasta cierto punto), pero estoy buscando técnicas que se puedan aplicar en todos los ámbitos, especialmente en relación con funciones que se llaman muchas veces en bucles estrechos .
¿Qué técnicas puedo utilizar para reducir la cantidad de trabajo que debe hacer el recolector de basura?
Y, quizás también, ¿qué técnicas se pueden emplear para identificar qué objetos se recolectan más basura? (Es una base de código muy grande, por lo que comparar instantáneas del montón no ha sido muy fructífero)
fuente
Respuestas:
Muchas de las cosas que debe hacer para minimizar la rotación de GC van en contra de lo que se considera JS idiomático en la mayoría de los otros escenarios, así que tenga en cuenta el contexto al juzgar los consejos que doy.
La asignación ocurre en intérpretes modernos en varios lugares:
new
o mediante sintaxis literal[...]
, o{}
.(function (...) { ... })
.Object(myNumber)
oNumber.prototype.toString.call(42)
Array.prototype.slice
.arguments
para reflexionar sobre la lista de parámetros.Evite hacer eso, y agrupe y reutilice objetos cuando sea posible.
Específicamente, busque oportunidades para:
split
coincidencias de expresiones regulares ya que cada uno requiere múltiples asignaciones de objetos. Esto sucede con frecuencia con claves en tablas de búsqueda e ID de nodo DOM dinámico. Por ejemplo,lookupTable['foo-' + x]
ydocument.getElementById('foo-' + x)
ambos implican una asignación, ya que hay una concatenación de cadenas. A menudo, puede adjuntar claves a objetos de larga duración en lugar de volver a concatenarlos. Dependiendo de los navegadores que necesite admitir, es posible que puedaMap
utilizar objetos como claves directamente.try { op(x) } catch (e) { ... }
hacerloif (!opCouldFailOn(x)) { op(x); } else { ... }
.JSON.stringify
que utiliza un búfer nativo interno para acumular contenido en lugar de asignar varios objetos.arguments
funciones since que usan que tienen que crear un objeto similar a una matriz cuando se llaman.Sugerí usarlo
JSON.stringify
para crear mensajes de red salientes. El análisis de los mensajes de entrada utilizandoJSON.parse
obviamente implica asignación, y mucho para mensajes grandes. Si puede representar sus mensajes entrantes como matrices de primitivas, puede guardar muchas asignaciones. El único otro componente integrado alrededor del cual puede construir un analizador que no asigna esString.prototype.charCodeAt
. Sin embargo, un analizador para un formato complejo que solo usa ese será un infierno de leer.fuente
JSON.parse
objetos d asignan menos (o igual) espacio que la cadena del mensaje?Las herramientas para desarrolladores de Chrome tienen una característica muy buena para rastrear la asignación de memoria. Se llama Línea de tiempo de la memoria. Este artículo describe algunos detalles. ¿Supongo que esto es de lo que estás hablando sobre el "diente de sierra"? Este es un comportamiento normal para la mayoría de los tiempos de ejecución con GC. La asignación continúa hasta que se alcanza un umbral de uso que activa una recopilación. Normalmente hay diferentes tipos de colecciones en diferentes umbrales.
Las recolecciones de basura se incluyen en la lista de eventos asociada con el seguimiento junto con su duración. En mi portátil bastante viejo, las colecciones efímeras se producen a unos 4 Mb y tardan 30 ms. Estas son 2 de sus iteraciones de bucle de 60 Hz. Si se trata de una animación, las colecciones de 30 ms probablemente estén causando tartamudeo. Debe comenzar aquí para ver qué está sucediendo en su entorno: dónde está el umbral de recolección y cuánto tiempo están tomando sus recolecciones. Esto le brinda un punto de referencia para evaluar las optimizaciones. Pero probablemente no hará nada mejor que disminuir la frecuencia de la tartamudez al disminuir la tasa de asignación, alargando el intervalo entre colecciones.
El siguiente paso es utilizar los perfiles | Función Record Heap Allocations para generar un catálogo de asignaciones por tipo de registro. Esto mostrará rápidamente qué tipos de objetos consumen más memoria durante el período de seguimiento, lo que equivale a la tasa de asignación. Concéntrese en estos en orden descendente de tasa.
Las técnicas no son ciencia espacial. Evite los objetos en caja cuando puede hacerlo con uno sin caja. Utilice variables globales para retener y reutilizar objetos de un solo cuadro en lugar de asignar nuevos en cada iteración. Reúna tipos de objetos comunes en listas gratuitas en lugar de abandonarlos. Almacene en caché los resultados de la concatenación de cadenas que probablemente sean reutilizables en iteraciones futuras. Evite la asignación solo para devolver resultados de función estableciendo variables en un ámbito adjunto. Deberá considerar cada tipo de objeto en su propio contexto para encontrar la mejor estrategia. Si necesita ayuda con detalles específicos, publique una edición que describa los detalles del desafío que está viendo.
Aconsejo no pervertir su estilo de codificación normal a lo largo de una aplicación en un intento por producir menos basura. Esto es por la misma razón por la que no debe optimizar la velocidad antes de tiempo. La mayor parte de su esfuerzo más gran parte de la complejidad y oscuridad añadidas del código no tendrán sentido.
fuente
request animation frame
,animation frame fired
ycomposite layers
. No tengo idea de por qué no veoGC Event
como tú (esto es en la última versión de Chrome, y también en canary).@342342
ycode relocation info
.Como principio general, querrá almacenar en caché tanto como sea posible y hacer la menor cantidad de creación y destrucción para cada ejecución de su ciclo.
Lo primero que me viene a la cabeza es reducir el uso de funciones anónimas (si las tiene) dentro de su bucle principal. También sería fácil caer en la trampa de crear y destruir objetos que pasan a otras funciones. De ninguna manera soy un experto en javascript, pero me imagino que esto:
correría mucho más rápido que esto:
¿Hay algún tiempo de inactividad para su programa? ¿Quizás necesite que se ejecute sin problemas durante uno o dos segundos (por ejemplo, para una animación) y luego tenga más tiempo para procesar? Si este es el caso, podría ver tomar objetos que normalmente serían basura recolectada a lo largo de la animación y mantener una referencia a ellos en algún objeto global. Luego, cuando termine la animación, puede borrar todas las referencias y dejar que el recolector de basura haga su trabajo.
Lo siento si todo esto es un poco trivial en comparación con lo que ya ha probado y pensado.
fuente
Haría uno o algunos objetos en el
global scope
(donde estoy seguro de que el recolector de basura no puede tocarlos), luego trataría de refactorizar mi solución para usar esos objetos para hacer el trabajo, en lugar de usar variables locales .Por supuesto, no se podría hacer en todas partes del código, pero generalmente esa es mi manera de evitar el recolector de basura.
PD: Podría hacer que esa parte específica del código sea un poco menos fácil de mantener.
fuente