From 48e87ca5e48f7669a2a0f5cf14f165df25acaa88 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Sun, 21 Jan 2024 22:10:45 -0500 Subject: [PATCH] Make mergeFunction fast. Before: https://share.firefox.dev/3SbzpQy (1422 samples in mergeFunction) After: https://share.firefox.dev/3tVp3fQ (46 samples in mergeFunction, 30x faster) --- src/profile-logic/transforms.js | 75 ++++--- .../__snapshots__/profile-view.test.js.snap | 193 +++++++++++------- 2 files changed, 164 insertions(+), 104 deletions(-) diff --git a/src/profile-logic/transforms.js b/src/profile-logic/transforms.js index b5cc20ebfa..5f0ec3d565 100644 --- a/src/profile-logic/transforms.js +++ b/src/profile-logic/transforms.js @@ -794,7 +794,7 @@ export function mergeCallNode( } /** - * Go through the StackTable and remove any stacks that are part of the given function. + * Go through the StackTable and "skip" any stacks with the given function. * This operation effectively merges the timing of the stacks into their callers. */ export function mergeFunction( @@ -802,45 +802,56 @@ export function mergeFunction( funcIndexToMerge: IndexIntoFuncTable ): Thread { const { stackTable, frameTable } = thread; - const oldStackToNewStack: Map< - IndexIntoStackTable | null, - IndexIntoStackTable | null, - > = new Map(); - // A root stack's prefix will be null. Maintain that relationship from old to new - // stacks by mapping from null to null. - oldStackToNewStack.set(null, null); - const newStackTable = getEmptyStackTable(); + + // A map oldStack -> newStack+1, implemented as a Uint32Array for performance. + // If newStack+1 is zero it means "null", i.e. this stack was filtered out. + // Typed arrays are initialized to zero, which we interpret as null. + // + // For each old stack, the new stack is computed as follows: + // - If the old stack's function is not funcIndexToMerge, then the new stack + // is the same as the old stack. + // - If the old stack's function is funcIndexToMerge, then the new stack is + // the closest ancestor whose func is not funcIndexToMerge, or null if no + // such ancestor exists. + // + // We only compute a new prefix column; the other columns are copied from the + // old stack table. The skipped stacks are "orphaned"; they'll still be present + // in the new stack table but not referenced by samples or other stacks. + const oldStackToNewStackPlusOne = new Uint32Array(stackTable.length); + + const stackTableFrameCol = stackTable.frame; + const frameTableFuncCol = frameTable.func; + const oldPrefixCol = stackTable.prefix; + const newPrefixCol = new Array(stackTable.length); for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) { - const prefix = stackTable.prefix[stackIndex]; - const frameIndex = stackTable.frame[stackIndex]; - const category = stackTable.category[stackIndex]; - const subcategory = stackTable.subcategory[stackIndex]; - const funcIndex = frameTable.func[frameIndex]; + const oldPrefix = oldPrefixCol[stackIndex]; + const newPrefixPlusOne = + oldPrefix === null ? 0 : oldStackToNewStackPlusOne[oldPrefix]; + const frameIndex = stackTableFrameCol[stackIndex]; + const funcIndex = frameTableFuncCol[frameIndex]; if (funcIndex === funcIndexToMerge) { - const newStackPrefix = oldStackToNewStack.get(prefix); - oldStackToNewStack.set( - stackIndex, - newStackPrefix === undefined ? null : newStackPrefix - ); + oldStackToNewStackPlusOne[stackIndex] = newPrefixPlusOne; } else { - const newStackIndex = newStackTable.length++; - const newStackPrefix = oldStackToNewStack.get(prefix); - newStackTable.prefix[newStackIndex] = - newStackPrefix === undefined ? null : newStackPrefix; - newStackTable.frame[newStackIndex] = frameIndex; - newStackTable.category[newStackIndex] = category; - newStackTable.subcategory[newStackIndex] = subcategory; - oldStackToNewStack.set(stackIndex, newStackIndex); + oldStackToNewStackPlusOne[stackIndex] = stackIndex + 1; } + const newPrefix = newPrefixPlusOne === 0 ? null : newPrefixPlusOne - 1; + newPrefixCol[stackIndex] = newPrefix; } - return updateThreadStacks( - thread, - newStackTable, - getMapStackUpdater(oldStackToNewStack) - ); + const newStackTable = { + ...stackTable, + prefix: newPrefixCol, + }; + + return updateThreadStacks(thread, newStackTable, (oldStack) => { + if (oldStack === null) { + return null; + } + const newStackPlusOne = oldStackToNewStackPlusOne[oldStack]; + return newStackPlusOne === 0 ? null : newStackPlusOne - 1; + }); } /** diff --git a/src/test/store/__snapshots__/profile-view.test.js.snap b/src/test/store/__snapshots__/profile-view.test.js.snap index 8b7999d51d..04a4821036 100644 --- a/src/test/store/__snapshots__/profile-view.test.js.snap +++ b/src/test/store/__snapshots__/profile-view.test.js.snap @@ -2193,11 +2193,13 @@ Object { 0, 0, 0, + 0, ], "depth": Array [ 0, 1, 2, + 2, 3, 2, 3, @@ -2207,6 +2209,7 @@ Object { "func": Int32Array [ 0, 1, + 2, 3, 4, 5, @@ -2220,18 +2223,20 @@ Object { 0, 0, 0, + 0, 2, 0, 0, ], - "length": 8, + "length": 9, "maxDepth": 3, "nextSibling": Int32Array [ -1, -1, - 4, + 3, + 5, -1, - 6, + 7, -1, -1, -1, @@ -2240,11 +2245,12 @@ Object { -1, 0, 1, - 2, 1, - 4, + 3, 1, - 6, + 5, + 1, + 7, ], "sourceFramesInlinedIntoSymbol": Array [ null, @@ -2255,6 +2261,7 @@ Object { null, null, null, + null, ], "subcategory": Int32Array [ 0, @@ -2265,16 +2272,18 @@ Object { 0, 0, 0, + 0, ], "subtreeRangeEnd": Uint32Array [ - 8, - 8, - 4, - 4, - 6, - 6, - 8, - 8, + 9, + 9, + 3, + 5, + 5, + 7, + 7, + 9, + 9, ], }, "isInverted": false, @@ -2287,6 +2296,7 @@ Object { 5, 6, 7, + 8, ], } `; @@ -2298,6 +2308,7 @@ CallTree { 1, 0, 0, + 0, 1, 0, 0, @@ -2314,11 +2325,13 @@ CallTree { 0, 0, 0, + 0, ], "depth": Array [ 0, 1, 2, + 2, 3, 2, 3, @@ -2328,6 +2341,7 @@ CallTree { "func": Int32Array [ 0, 1, + 2, 3, 4, 5, @@ -2341,18 +2355,20 @@ CallTree { 0, 0, 0, + 0, 2, 0, 0, ], - "length": 8, + "length": 9, "maxDepth": 3, "nextSibling": Int32Array [ -1, -1, - 4, + 3, + 5, -1, - 6, + 7, -1, -1, -1, @@ -2361,11 +2377,12 @@ CallTree { -1, 0, 1, - 2, 1, - 4, + 3, 1, - 6, + 5, + 1, + 7, ], "sourceFramesInlinedIntoSymbol": Array [ null, @@ -2376,6 +2393,7 @@ CallTree { null, null, null, + null, ], "subcategory": Int32Array [ 0, @@ -2386,16 +2404,18 @@ CallTree { 0, 0, 0, + 0, ], "subtreeRangeEnd": Uint32Array [ - 8, - 8, - 4, - 4, - 6, - 6, - 8, - 8, + 9, + 9, + 3, + 5, + 5, + 7, + 7, + 9, + 9, ], }, "isInverted": false, @@ -2408,6 +2428,7 @@ CallTree { 5, 6, 7, + 8, ], }, "_callNodeSummary": Object { @@ -2417,6 +2438,7 @@ CallTree { 0, 0, 0, + 0, 2, 0, 0, @@ -2427,6 +2449,7 @@ CallTree { 0, 0, 0, + 0, 2, 0, 0, @@ -2436,6 +2459,7 @@ CallTree { 2, 0, 0, + 0, 2, 2, 0, @@ -2452,11 +2476,13 @@ CallTree { 0, 0, 0, + 0, ], "depth": Array [ 0, 1, 2, + 2, 3, 2, 3, @@ -2466,6 +2492,7 @@ CallTree { "func": Int32Array [ 0, 1, + 2, 3, 4, 5, @@ -2479,18 +2506,20 @@ CallTree { 0, 0, 0, + 0, 2, 0, 0, ], - "length": 8, + "length": 9, "maxDepth": 3, "nextSibling": Int32Array [ -1, -1, - 4, + 3, + 5, -1, - 6, + 7, -1, -1, -1, @@ -2499,11 +2528,12 @@ CallTree { -1, 0, 1, - 2, 1, - 4, + 3, 1, - 6, + 5, + 1, + 7, ], "sourceFramesInlinedIntoSymbol": Array [ null, @@ -2514,6 +2544,7 @@ CallTree { null, null, null, + null, ], "subcategory": Int32Array [ 0, @@ -2524,16 +2555,18 @@ CallTree { 0, 0, 0, + 0, ], "subtreeRangeEnd": Uint32Array [ - 8, - 8, - 4, - 4, - 6, - 6, - 8, - 8, + 9, + 9, + 3, + 5, + 5, + 7, + 7, + 9, + 9, ], }, "_categories": Array [ @@ -2831,8 +2864,8 @@ CallTree { ], "length": 2, "stack": Array [ - 5, - 5, + 6, + 6, ], "time": Array [ 4, @@ -2851,10 +2884,12 @@ CallTree { 0, 0, 0, + 0, ], "frame": Array [ 0, 1, + 2, 3, 4, 5, @@ -2862,16 +2897,17 @@ CallTree { 7, 8, ], - "length": 8, + "length": 9, "prefix": Array [ null, 0, 1, - 2, 1, - 4, + 3, 1, - 6, + 5, + 1, + 7, ], "subcategory": Array [ 0, @@ -2882,6 +2918,7 @@ CallTree { 0, 0, 0, + 0, ], }, "stringTable": UniqueStringArray { @@ -3150,10 +3187,10 @@ Object { ], "length": 4, "stack": Array [ - 5, - 5, - 5, - 7, + 6, + 6, + 6, + 8, ], "time": Array [ 3, @@ -3174,10 +3211,12 @@ Object { 0, 0, 0, + 0, ], "frame": Array [ 0, 1, + 2, 3, 4, 5, @@ -3185,16 +3224,17 @@ Object { 7, 8, ], - "length": 8, + "length": 9, "prefix": Array [ null, 0, 1, - 2, 1, - 4, + 3, 1, - 6, + 5, + 1, + 7, ], "subcategory": Array [ 0, @@ -3205,6 +3245,7 @@ Object { 0, 0, 0, + 0, ], }, "stringTable": UniqueStringArray { @@ -3270,7 +3311,7 @@ Array [ }, Object { "callNode": Array [ - 4, + 5, ], "end": Array [ 1, @@ -3285,7 +3326,7 @@ Array [ }, Object { "callNode": Array [ - 5, + 6, ], "end": Array [ 1, @@ -3549,8 +3590,8 @@ Object { ], "length": 2, "stack": Array [ - 5, - 5, + 6, + 6, ], "time": Array [ 4, @@ -3569,10 +3610,12 @@ Object { 0, 0, 0, + 0, ], "frame": Array [ 0, 1, + 2, 3, 4, 5, @@ -3580,16 +3623,17 @@ Object { 7, 8, ], - "length": 8, + "length": 9, "prefix": Array [ null, 0, 1, - 2, 1, - 4, + 3, 1, - 6, + 5, + 1, + 7, ], "subcategory": Array [ 0, @@ -3600,6 +3644,7 @@ Object { 0, 0, 0, + 0, ], }, "stringTable": UniqueStringArray { @@ -3866,10 +3911,10 @@ Object { ], "length": 4, "stack": Array [ - 5, - 5, - 5, - 7, + 6, + 6, + 6, + 8, ], "time": Array [ 3, @@ -3890,10 +3935,12 @@ Object { 0, 0, 0, + 0, ], "frame": Array [ 0, 1, + 2, 3, 4, 5, @@ -3901,16 +3948,17 @@ Object { 7, 8, ], - "length": 8, + "length": 9, "prefix": Array [ null, 0, 1, - 2, 1, - 4, + 3, 1, - 6, + 5, + 1, + 7, ], "subcategory": Array [ 0, @@ -3921,6 +3969,7 @@ Object { 0, 0, 0, + 0, ], }, "stringTable": UniqueStringArray {