为什么 Math.random()(在 Chrome 中)分配需要垃圾收集器 (gc) 清理的内存?

Why does Math.random() (in Chrome) allocate memory that needs cleanup by the Garbage Collector (gc)?

故事

在一些性能关键代码的测试中,我观察到 Math.random() 的副作用,我不明白。我在找

问题

看起来调用 Math.random() 分配了一些需要由 Gargabe 收集器 (gc) 清理的内存。

测试:用Math.random()

    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);
    });

观察 1:Chrome 个人资料

Chrome: 95.0.463869, Windows 10, 边缘: 95.0.1020.40

运行浏览器中的这段代码并记录性能配置文件将导致经典内存之字形

Memory profile of Math.random() test

意见 2:Firefox

Firefox 开发人员:95,Windows10

未检测到垃圾收集 (CC/GCMinor) - 内存相当线性

解决方法

crypto.getRandomValues()

使用 self.crypto.getRandomValues` 将 Math.random() 替换为足够大的预先计算的随机数数组。

(此处为 V8 开发人员。)

是的,这是预期的。这是一个(非常基本的)设计决策,不是错误,并且与 Math.random() 没有严格关系。 V8 将浮点数“装箱”为堆上的对象。那是因为它在一个对象中每个字段使用 32 位,这对于 64 位双精度显然是不够的,并且间接层解决了这个问题。

有很多特殊情况可以避免这种装箱:

  • 在优化代码中,对于永远不会离开当前函数的值。
  • 对于数值足够小的整数(“Smis”,带符号的 31 位整数范围)的数字。
  • 对于数组中仅将数字视为元素的元素(例如 [1, 2.5, NaN],而不是 [1, true, "hello"])。
  • 可能是我现在没有想到的其他情况。此外,所有这些内部细节都可以(并且确实!)随着时间的推移而改变。

Firefox 使用一种根本不同的技术来存储内部引用。好处是它避免了必须将数字装箱,缺点是它为非数字的东西使用了更多的内存。严格来说,这两种方法都不比另一种好,只是权衡不同而已。

一般来说你不必担心这个,这只是你的 JavaScript 引擎在做它的事情:-)

Problem: Running this code in the browser and record a performance profile will result in a classic memory zig-zag

为什么这是个问题?这就是垃圾回收内存的工作原理。 (此外,为了正确看待事情:GC 在您的配置文件中每 ~8 秒仅花费 ~0.3 毫秒。)

Workaround: Replace Math.random() with a large enough array of pre-calculated random numbers using self.crypto.getRandomValues`.

用一个大而长寿命的数组替换微小的短寿命 HeapNumbers 听起来不是节省内存的好方法。

如果真的很重要,避免数字装箱的一种方法是将它们存储在数组中而不是作为对象属性。但在你的代码中经历难以维护的扭曲之前,一定要衡量它是否对你的应用程序真的很重要。在微基准测试中很容易证明巨大的影响,但很少看到它在实际应用程序中产生很大的影响。