Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 3e06cfc

Browse files
lkollarpablogsal
andauthored
gh-135953: Reduce memory usage of stack collectors (#138875)
The stack collector base class keeps all frames until export() is called, which causes significant unnecessary memory usage. Instead, we can process the frames on the fly in the collect call by dispatching the aggregation logic to the subclass through the process_frames method. Co-authored-by: Pablo Galindo Salgado <pablogsal@gmail.com>
1 parent efc08c5 commit 3e06cfc

File tree

5 files changed

+292
-174
lines changed

5 files changed

+292
-174
lines changed

‎Lib/profiling/sampling/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
from .collector import Collector
88
from .pstats_collector import PstatsCollector
99
from .stack_collector import CollapsedStackCollector
10+
from .string_table import StringTable
1011

11-
__all__ = ("Collector", "PstatsCollector", "CollapsedStackCollector")
12+
__all__ = ("Collector", "PstatsCollector", "CollapsedStackCollector", "StringTable")

‎Lib/profiling/sampling/flamegraph.js

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,50 @@
11
const EMBEDDED_DATA = {{FLAMEGRAPH_DATA}};
22

3+
// Global string table for resolving string indices
4+
let stringTable = [];
5+
6+
// Function to resolve string indices to actual strings
7+
function resolveString(index) {
8+
if (typeof index === 'number' && index >= 0 && index < stringTable.length) {
9+
return stringTable[index];
10+
}
11+
// Fallback for non-indexed strings or invalid indices
12+
return String(index);
13+
}
14+
15+
// Function to recursively resolve all string indices in flamegraph data
16+
function resolveStringIndices(node) {
17+
if (!node) return node;
18+
19+
// Create a copy to avoid mutating the original
20+
const resolved = { ...node };
21+
22+
// Resolve string fields
23+
if (typeof resolved.name === 'number') {
24+
resolved.name = resolveString(resolved.name);
25+
}
26+
if (typeof resolved.filename === 'number') {
27+
resolved.filename = resolveString(resolved.filename);
28+
}
29+
if (typeof resolved.funcname === 'number') {
30+
resolved.funcname = resolveString(resolved.funcname);
31+
}
32+
33+
// Resolve source lines if present
34+
if (Array.isArray(resolved.source)) {
35+
resolved.source = resolved.source.map(index =>
36+
typeof index === 'number' ? resolveString(index) : index
37+
);
38+
}
39+
40+
// Recursively resolve children
41+
if (Array.isArray(resolved.children)) {
42+
resolved.children = resolved.children.map(child => resolveStringIndices(child));
43+
}
44+
45+
return resolved;
46+
}
47+
348
// Python color palette - cold to hot
449
const pythonColors = [
550
"#fff4bf", // Coldest - light yellow (<1%)
@@ -100,6 +145,10 @@ function createPythonTooltip(data) {
100145
</div>`;
101146
}
102147

148+
// Resolve strings for display
149+
const funcname = resolveString(d.data.funcname) || resolveString(d.data.name);
150+
const filename = resolveString(d.data.filename) || "";
151+
103152
const tooltipHTML = `
104153
<div>
105154
<div style="color: #3776ab; font-weight: 600; font-size: 16px;
@@ -257,9 +306,9 @@ function updateSearchHighlight(searchTerm, searchInput) {
257306
let matchCount = 0;
258307
d3.selectAll("#chart rect").each(function (d) {
259308
if (d && d.data) {
260-
const name = d.data.name || "";
261-
const funcname = d.data.funcname || "";
262-
const filename = d.data.filename || "";
309+
const name = resolveString(d.data.name) || "";
310+
const funcname = resolveString(d.data.funcname) || "";
311+
const filename = resolveString(d.data.filename) || "";
263312
const term = searchTerm.toLowerCase();
264313
const matches =
265314
name.toLowerCase().includes(term) ||
@@ -317,12 +366,20 @@ function handleResize(chart, data) {
317366

318367
function initFlamegraph() {
319368
ensureLibraryLoaded();
320-
const tooltip = createPythonTooltip(EMBEDDED_DATA);
321-
const chart = createFlamegraph(tooltip, EMBEDDED_DATA.value);
322-
renderFlamegraph(chart, EMBEDDED_DATA);
369+
370+
// Extract string table if present and resolve string indices
371+
let processedData = EMBEDDED_DATA;
372+
if (EMBEDDED_DATA.strings) {
373+
stringTable = EMBEDDED_DATA.strings;
374+
processedData = resolveStringIndices(EMBEDDED_DATA);
375+
}
376+
377+
const tooltip = createPythonTooltip(processedData);
378+
const chart = createFlamegraph(tooltip, processedData.value);
379+
renderFlamegraph(chart, processedData);
323380
attachPanelControls();
324381
initSearchHandlers();
325-
handleResize(chart, EMBEDDED_DATA);
382+
handleResize(chart, processedData);
326383
}
327384

328385
if (document.readyState === "loading") {
@@ -338,7 +395,10 @@ function populateStats(data) {
338395
const functionMap = new Map();
339396

340397
function collectFunctions(node) {
341-
if (node.filename && node.funcname) {
398+
const filename = resolveString(node.filename);
399+
const funcname = resolveString(node.funcname);
400+
401+
if (filename && funcname) {
342402
// Calculate direct samples (this node's value minus children's values)
343403
let childrenValue = 0;
344404
if (node.children) {
@@ -347,23 +407,23 @@ function populateStats(data) {
347407
const directSamples = Math.max(0, node.value - childrenValue);
348408

349409
// Use file:line:funcname as key to ensure uniqueness
350-
const funcKey = `${node.filename}:${node.lineno || '?'}:${node.funcname}`;
410+
const funcKey = `${filename}:${node.lineno || '?'}:${funcname}`;
351411

352412
if (functionMap.has(funcKey)) {
353413
const existing = functionMap.get(funcKey);
354414
existing.directSamples += directSamples;
355415
existing.directPercent = (existing.directSamples / totalSamples) * 100;
356416
// Keep the most representative file/line (the one with more samples)
357417
if (directSamples > existing.maxSingleSamples) {
358-
existing.filename = node.filename;
418+
existing.filename = filename;
359419
existing.lineno = node.lineno || '?';
360420
existing.maxSingleSamples = directSamples;
361421
}
362422
} else {
363423
functionMap.set(funcKey, {
364-
filename: node.filename,
424+
filename: filename,
365425
lineno: node.lineno || '?',
366-
funcname: node.funcname,
426+
funcname: funcname,
367427
directSamples,
368428
directPercent: (directSamples / totalSamples) * 100,
369429
maxSingleSamples: directSamples

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /