如何测量单个函数使用的总内存



我想知道如何在JavaScript中测量函数使用的总内存。我发现有一个APIperformance.memory。我注意到usedJSHeapSize不仅适用于单个页面,还可以在其他选项卡之间共享摘要。假设在我的情况下,我总是只打开一个选项卡,所以我应该只测量我感兴趣的这一个选项卡的内存。我还读到,我应该在函数执行后等待一段时间,让垃圾收集器清除所有未使用的内存。最后,我想出了下面的一个解决方案,但我不知道这是一个好主意吗?我测量记忆的方法是正确的。如果你对此有任何想法,请告诉我。

function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function fun1() {
// function logic...
}
function fun2() {
// function logic...
}

function measureMemory(functionToMeasure) {
const memoryStart = performance.memory.usedJSHeapSize;
functionToMeasure();
const memoryEnd = performance.memory.usedJSHeapSize;
return memoryEnd - memoryStart;
}

async function runBenchmark() {
const totalMemoryUsedByFun1 = measureMemory(fun1);
// I wait 2min to be sure garbage collector clean all unused memory
await wait(120000);
const totalMemoryUsedByFun2 = measureMemory(fun2);
}

你认为这是一种测量函数使用的记忆的好方法/想法吗?2分钟是否足够垃圾收集器清除未使用的内存?

我担心的例子:

function fun1() {
// alocates 1000KB
}
function fun2() {
// alocates 300KB
}
// initial heap size = 0
const memoryUsedByFun1 = measureMemory(fun1());
// Heap size is equal 1000KB
// Now let's say I do not wait for GC and only 500KB have been released
// heap size before measuring fun2 is equal to 500KB
const memoryUsedByFun2 = measureMemory(fun2());
// While fun2 is running, 500KB of fun1 allocations have been released
// So now if I do memoryStart - memoryEnd 
// Where memory start was 500KB and memory end is equal to 300KB 
// because 500KB was released while computing fun2 
// I will get result 500KB - 300KB = 200KB 
// What is not true because fun2 allocates 300KB)

我也看到有这样的APImeasureUserAgentSpecificMemory(),但我认为即使打开#experimental-web-platform-features,这个API也根本不起作用

(此处为V8开发人员。)

你担心是完全正确的;这行不通。

一个原因是performance.memory(故意)不够精确,无法支持这种模式。打开DevTools控制台,自己看看:

> var before = performance.memory.usedJSHeapSize; 
new Array(1000);
console.log(performance.memory.usedJSHeapSize - before);
< 0

(您希望为该数组获得4024字节,另外可能还有更多的字节用于检索和打印堆大小。)

另一个原因是,在函数仍在运行时,无法阻止GC收集短暂的垃圾。

建议的measureUserAgentSpecificMemoryAPI不会解决这两个问题,正如它的描述所告诉你的那样。

手动控制GC循环通常是一个好主意;实验室条件";这样的测试,但等待两分钟并不是实现这一目标的有效方法。用口语来说,想象一下下面的对话:;GC,我们有一分钟的空闲时间,想做点工作吗"quot;不,自上次运行以来只分配了500KB,如果检查这些是否已经是垃圾,那将是浪费CPU周期">
相反,使用--expose-gc运行V8以获得一个可以随时调用的gc()函数。结合--trace-gc,这是我能想到的最好的方式来完成你在这里想要做的事情。

强制性免责声明:您不想在生产代码中手动触发GC周期;这只适用于为目标测试创建非常特定的情况

例如,以以下代码为例:

function f1() {
for (let i = 0; i < 1000; i++) new Array(1000);
}
gc();
console.log(">>> START");
f1();
gc();
console.log(">>> END");

并在V8的d8shell或使用--expose-gc --trace-gcnode中运行它。你会得到这样的东西:

[14531:0x556bc769a540]        2 ms: Mark-sweep 0.9 (1.9) -> 0.5 (2.9) MB, 0.6 / 0.0 ms  (average mu = 1.000, current mu = 1.000) testing; GC in old space requested
>>> START
[14531:0x556bc769a540]        3 ms: Scavenge 1.6 (2.9) -> 0.6 (2.9) MB, 0.0 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure; 
[14531:0x556bc769a540]        3 ms: Scavenge 1.6 (2.9) -> 0.6 (2.9) MB, 0.0 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure; 
[14531:0x556bc769a540]        3 ms: Scavenge 1.6 (2.9) -> 0.6 (2.9) MB, 0.0 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure; 
[14531:0x556bc769a540]        3 ms: Scavenge 1.6 (2.9) -> 0.6 (2.9) MB, 0.0 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure; 
[14531:0x556bc769a540]        3 ms: Mark-sweep 0.6 (2.9) -> 0.5 (2.9) MB, 0.2 / 0.0 ms  (average mu = 0.765, current mu = 0.765) testing; GC in old space requested
>>> END

然后在一个循环之后和下一个循环开始时将各个堆大小之间的差异相加;CCD_ 15";并从先前的"CCD_ 16"中减去CCD_;CCD_ 17">
总的来说,这是(1.6 - 0.5) + (1.6 - 0.6) + (1.6 - 0.6) + (1.6 - 0.6) + (0.6 - 0.6) == 4.1 MB,相当接近阵列所需的4.02 MB。(我不确定额外的0.08MB是从哪里来的;可能是测量/舍入错误。)


很遗憾,你一开始就没有描述你为什么关心这件事;这可能是一种不太有希望的方法来解决你最初的问题;这个函数分配多少&";,因为(1)JavaScript引擎有时会为内部元数据分配对象(这可能发生在函数的一次运行中,但不会发生在另一次运行),以及(2)在函数优化后,它通常会比未优化的版本分配更少(因为某些分配有时可以优化掉)。

前者的后果是,你可能很容易被明显不一致的测量结果弄糊涂;后者的结果是,当您试图使性能敏感的代码尽可能接近于无分配时,但您只关注未优化的执行,一旦优化器启动,你可能会把时间浪费在无关紧要的事情上

最新更新