故事
在对性能关键代码进行一些测试期间,我观察到 Math.random() 的副作用我不明白。我正在寻找
- 一些深入的技术解释
- 对我的测试(或期望)的伪造
- 指向 V8 问题/错误单的链接
问题
看起来调用 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、Edge:95.0.1020.40
在浏览器中运行此代码并记录性能配置文件将导致经典的内存之字形
Memory profile of Math.random() test
观察 2:Firefox
Firefox 开发者:95、Windows 10
未检测到垃圾收集 (CC/GCMinor) - 内存相当线性
解决方法
crypto.getRandomValues()
使用
self.crypto.getRandomValues` 将 Math.random()
替换为足够大的预先计算的随机数数组。
最佳答案
(这里是 V8 开发人员。)
是的,这是预期的。这是一个(非常基本的)设计决策,而不是错误,并且与 Math.random() 没有严格关系。 V8 将 float “装箱”为堆上的对象。这是因为它在对象中的每个字段使用 32 位,这对于 64 位 double 显然是不够的,并且间接层解决了这个问题。
在许多特殊情况下可以避免这种装箱:
- 在优化代码中,对于永远不会离开当前函数的值。
- 对于数值足够小的整数(“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 听起来并不是一个节省内存的好方法。
如果真的很重要,避免数字装箱的一种方法是将它们存储在数组中而不是作为对象属性。但在代码中进行难以维护的扭曲之前,请务必衡量它对您的应用程序是否真的很重要。在微基准测试中很容易展示巨大的效果,但很少看到它在实际应用程序中产生很大的影响。
关于javascript - 为什么 Math.random()(在 Chrome 中)分配需要垃圾收集器 (gc) 清理的内存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69829416/