首页 > 解决方案 > 为什么 Math.random()(在 Chrome 中)分配需要垃圾收集器 (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 配置文件

铬:95.0.463869,Windows 10,边缘:95.0.1020.40

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

Math.random() 测试的内存配置文件

观察 2:火狐

火狐开发者:95,Windows 10

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

解决方法

crypto.getRandomValues()

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

标签: javascriptperformancerandomgarbage-collectionv8

解决方案


(这里是 V8 开发人员。)

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

有一些特殊情况可以避免这种拳击:

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

Firefox 使用一种完全不同的技术来存储内部引用。好处是它避免了将数字装箱,缺点是它使用更多的内存来存储不是数字的东西。两种方法都没有比另一种更好,只是一种不同的权衡。

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

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

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

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

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

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


推荐阅读