mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
perf_hooks: report histogram memory to V8
The native object behind histograms created by `createHistogram()`, `importHistogram()`, and `snapshot()` did not report the memory of its HDR histogram to V8. That memory is about 352 KiB with the default options, and V8 did not take it into account when scheduling garbage collection. Short-lived histograms could therefore hold on to a large amount of native memory until an unrelated garbage collection: in a loop taking 2,000 snapshots, RSS grew by 477 MiB. Report the size of the native histogram while the object is alive, as `SlidingWindowHistogram` already does for its chunks. Objects that share a native histogram after cloning each report its full size. With this change, RSS grows by 8 MiB in the same loop. Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: OpenCode PR-URL: https://github.com/nodejs/node/pull/66099 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
1 parent
b9e257f5cb
commit
7d2b7e178d
3 files changed
+61
No files matched your search
@@ -2032,6 +2032,7 @@ HistogramBase::HistogramBase(Environment* env,
|
||||
HistogramImpl::InternalFields::kImplField,
|
||||
static_cast<HistogramImpl*>(this),
|
||||
EmbedderDataTag::kDefault);
|
||||
ReportExternalMemory();
|
||||
}
|
||||
|
||||
HistogramBase::HistogramBase(Environment* env,
|
||||
@@ -2043,6 +2044,21 @@ HistogramBase::HistogramBase(Environment* env,
|
||||
HistogramImpl::InternalFields::kImplField,
|
||||
static_cast<HistogramImpl*>(this),
|
||||
EmbedderDataTag::kDefault);
|
||||
ReportExternalMemory();
|
||||
}
|
||||
|
||||
HistogramBase::~HistogramBase() {
|
||||
env()->external_memory_accounter()->Decrease(env()->isolate(),
|
||||
external_memory_);
|
||||
}
|
||||
|
||||
// Reports the size of the native histogram to V8 so that the garbage
|
||||
// collector accounts for it. Every object that refers to a native histogram
|
||||
// reports its full size, including objects that share one after cloning.
|
||||
void HistogramBase::ReportExternalMemory() {
|
||||
external_memory_ = histogram()->GetMemorySize();
|
||||
env()->external_memory_accounter()->Increase(env()->isolate(),
|
||||
external_memory_);
|
||||
}
|
||||
|
||||
void HistogramBase::MemoryInfo(MemoryTracker* tracker) const {
|
||||
|
||||
@@ -334,6 +334,8 @@ class HistogramBase final : public BaseObject, public HistogramImpl {
|
||||
v8::Local<v8::Object> wrap,
|
||||
std::shared_ptr<Histogram> histogram);
|
||||
|
||||
~HistogramBase() override;
|
||||
|
||||
BaseObject::TransferMode GetTransferMode() const override {
|
||||
return TransferMode::kCloneable;
|
||||
}
|
||||
@@ -361,6 +363,11 @@ class HistogramBase final : public BaseObject, public HistogramImpl {
|
||||
};
|
||||
|
||||
private:
|
||||
void ReportExternalMemory();
|
||||
|
||||
// The native memory reported to V8 while this object is alive.
|
||||
size_t external_memory_ = 0;
|
||||
|
||||
static v8::CFunction fast_record_;
|
||||
static v8::CFunction fast_record_delta_;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
// Tests that histogram objects report the memory of their native histogram to
|
||||
// V8, so that creating many short-lived histograms triggers garbage
|
||||
// collection.
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { setImmediate: setImmediatePromise } = require('timers/promises');
|
||||
const {
|
||||
PerformanceObserver,
|
||||
constants: { NODE_PERFORMANCE_GC_FLAGS_ALL_EXTERNAL_MEMORY },
|
||||
createHistogram,
|
||||
} = require('perf_hooks');
|
||||
|
||||
let externalMemoryGCs = 0;
|
||||
const observer = new PerformanceObserver((list) => {
|
||||
for (const entry of list.getEntries()) {
|
||||
if (entry.detail.flags & NODE_PERFORMANCE_GC_FLAGS_ALL_EXTERNAL_MEMORY)
|
||||
externalMemoryGCs++;
|
||||
}
|
||||
});
|
||||
observer.observe({ entryTypes: ['gc'] });
|
||||
|
||||
// With the default options, a histogram holds 45,056 64-bit counts, about
|
||||
// 352 KiB. V8 starts a garbage collection once external memory has grown by
|
||||
// 64 MiB, so 400 unreferenced snapshots (about 137 MiB) must trigger one.
|
||||
const histogram = createHistogram();
|
||||
for (let i = 0; i < 400; i++) histogram.snapshot();
|
||||
|
||||
(async () => {
|
||||
// Performance entries for garbage collections are delivered asynchronously.
|
||||
for (let i = 0; i < 10 && externalMemoryGCs === 0; i++)
|
||||
await setImmediatePromise();
|
||||
observer.disconnect();
|
||||
assert.ok(externalMemoryGCs > 0,
|
||||
'Expected a garbage collection caused by external memory');
|
||||
})().then(common.mustCall());
|
||||
Reference in new issue
Block a user