为什么迭代 Set 的值会分配并产生垃圾?

Why does iterating over a Set's values allocate and create garbage?

昨天我问 如何在不分配的情况下迭代 Map。 v8 开发人员回复如下:

But if you're only interested in processing the values anyway, you can avoid it being created by iterating over just the values: for (let v of map.values()) {...} does not allocate any short-lived objects. The same is true for iterating over map.keys().

我正在尝试在测试中复制它,但我遇到了麻烦。这是我制作的一个演示,它重现了我在使用简单 Set 时遇到的问题。将以下代码粘贴到一个空的 HTML 文件的脚本标签中:

let a = new Set([ 1, 2, 3, 4, 5 ]);
let b = [ 1, 2, 3, 4, 5 ];

let sum = 0;

function setIterate() {
  for (let item of a.values()) {
    sum += item;
  }
}

function arrayIterate() {
  for (let i = 0; i < b.length; i++) {
    sum += b[i];
  }
}

setInterval(setIterate, 1);
// setInterval(arrayIterate, 1);

如果你打开这个 HTML 文件然后点击 Shift+Escape 它应该打开 Chrome 任务管理器。如果您随后查看 Javascript Memory 列,您会看到内存随时间缓慢增长。

但是,如果您注释行 setInterval(setIterate, 1); 并取消注释行 setInterval(arrayIterate, 1); 并刷新页面,您将看到内存使用量保持不变。

也就是说,遍历 Set 随着时间的推移会产生垃圾堆积,而遍历数组则不会。

有什么方法可以迭代 Set 而不会随着时间的推移产生这种垃圾堆积?我正在开发一个浏览器游戏并在我的渲染循环中迭代许多 SetsMaps 并且由于 GC 运行.

偶尔会出现帧峰值

Why does iterating over a Set's values allocate and create garbage?

没有。使用 --trace-gc(在 d8node 中)对 setIterate 进行一百万次调用显示零 activity。 如果函数每次迭代分配一个字节(它不能;最小分配粒度是两个指针,即8个字节),那么年轻一代至少会填满那个时候有过一次。

Is there any way to iterate a Set without producing this garbage build-up over time?

当然;例如你是怎么做的。

why does the Task Manager show memory increasing over time?

我不认为它会那样做。我已经完全按原样复制粘贴了您的代码片段,并让它静置几分钟;该选项卡的 Chrome 任务管理器的“JavaScript 内存”列没有变化一千字节。 (这实际上只能证明这个测试不是真正的压力测试:一旦 sum 超过 1<<30,它 开始疯狂分配,但这不是因为集合或数组...)


大胆猜测:也许您安装了一些扩展程序,将代码注入每个页面并导致分配?无论如何,无论它是什么,它都不是 for..of 循环 Sets.