Durante algunas pruebas de un código crítico para el rendimiento, observé un efecto secundario de Math.random() que no entiendo. Busco
Parece que llamar a Math.random() asigna algo de memoria que Gargabe Collector (gc) debe limpiar.
const numberOfWrites = 100; const obj = { value: 0 }; let i = 0; function test() { for(i = 0; i < numberOfWrites; i++) { obj.value = Math.random(); } } window.addEventListener('DOMContentLoaded', () => { setInterval(() => { test(); }, 10); });Chrome: 95.0.463869, Windows 10, Borde: 95.0.1020.40
Ejecutar este código en el navegador y registrar un perfil de rendimiento dará como resultado un zig-zag de memoria clásico
Perfil de memoria de la prueba Math.random()
Desarrollador Firefox: 95, Windows 10
No se detectó recolección de basura (CC/GCMinor): la memoria es bastante lineal
Reemplace Math.random() con una matriz lo suficientemente grande de números aleatorios precalculados usando self.crypto.getRandomValues`.
(Desarrollador V8 aquí.)
Sí, esto es lo esperado. Es una decisión de diseño (bastante fundamental), no un error, y no está estrictamente relacionado con Math.random() . V8 "encajona" números de coma flotante como objetos en el montón. Eso es porque usa 32 bits por campo en un objeto, lo que obviamente no es suficiente para un doble de 64 bits, y una capa de direccionamiento indirecto resuelve eso.
Hay una serie de casos especiales en los que se puede evitar este boxeo:
[1, 2.5, NaN] , pero no [1, true, "hello"] ).Firefox utiliza una técnica fundamentalmente diferente para almacenar referencias internas. El beneficio es que evita tener que encuadrar números, el inconveniente es que usa más memoria para cosas que no son números. Ninguno de los enfoques es estrictamente mejor que el otro, es solo una compensación diferente.
En términos generales, se supone que no debe preocuparse por esto, es solo su motor JavaScript haciendo su trabajo :-)
Problema: ejecutar este código en el navegador y registrar un perfil de rendimiento dará como resultado un zig-zag de memoria clásico
¿Por que eso es un problema? Así es como funciona la memoria recolectada de basura. (Además, solo para poner las cosas en perspectiva: el GC solo gasta ~0.3ms cada ~8s en tu perfil).
Solución alternativa: reemplace Math.random() con una matriz lo suficientemente grande de números aleatorios precalculados usando self.crypto.getRandomValues`.
Reemplazar pequeños HeapNumbers de corta duración con una matriz grande y de larga duración no parece una buena manera de ahorrar memoria.
Si realmente importa, una forma de evitar el encajonamiento de números es almacenarlos en matrices en lugar de como propiedades de objetos. Pero antes de pasar por contorsiones difíciles de mantener en su código, asegúrese de medir si realmente es importante para su aplicación. Es fácil demostrar grandes efectos en un micropunto de referencia, es raro ver que tenga mucho impacto en aplicaciones reales.