diff --git a/src/profile-logic/call-node-info.js b/src/profile-logic/call-node-info.js index 2fb359bf7f..eaa8561fcd 100644 --- a/src/profile-logic/call-node-info.js +++ b/src/profile-logic/call-node-info.js @@ -449,13 +449,16 @@ export class CallNodeInfoInvertedImpl implements CallNodeInfoInverted { // The mapping of non-inverted stack index to non-inverted call node index. _stackIndexToNonInvertedCallNodeIndex: Int32Array; - // The number of roots, i.e. this._roots.length. + // The number of roots, which is also the number of functions. Each root of + // the inverted tree represents a "self" function, i.e. all call paths which + // end in a certain function. + // We have roots even for functions which aren't used as "self" functions in + // any sampled stacks, for simplicity. The actual displayed number of roots + // in the call tree will usually be lower because roots with a zero total sample + // count will be filtered out. But any data in this class is fully independent + // from sample counts. _rootCount: number; - // All inverted call tree roots. The roots of the inverted call tree are the - // "self" functions of the non-inverted call paths. - _roots: InvertedCallNodeHandle[]; - // This is a Map. // It lists the non-inverted call nodes in "suffix order", i.e. ordered by // comparing their call paths from back to front. @@ -497,16 +500,7 @@ export class CallNodeInfoInvertedImpl implements CallNodeInfoInverted { this._suffixOrderedCallNodes = suffixOrderedCallNodes; this._suffixOrderIndexes = suffixOrderIndexes; this._defaultCategory = defaultCategory; - - const rootCount = rootSuffixOrderIndexRangeEndCol.length; - this._rootCount = rootCount; - - const roots = new Array(rootCount); - for (let i = 0; i < rootCount; i++) { - roots[i] = i; - } - this._roots = roots; - + this._rootCount = rootSuffixOrderIndexRangeEndCol.length; this._funcCountBuf = funcCountBuf; this._funcCountBuf.fill(0); const invertedRootCallNodeTable = _createInvertedRootCallNodeTable( @@ -544,8 +538,8 @@ export class CallNodeInfoInvertedImpl implements CallNodeInfoInverted { return this._suffixOrderIndexes; } - getRoots(): Array { - return this._roots; + getFuncCount(): number { + return this._rootCount; } isRoot(nodeHandle: InvertedCallNodeHandle): boolean { diff --git a/src/profile-logic/call-tree.js b/src/profile-logic/call-tree.js index aeab56c65c..02132d2aab 100644 --- a/src/profile-logic/call-tree.js +++ b/src/profile-logic/call-tree.js @@ -58,8 +58,8 @@ export type CallTreeTimingsInverted = {| callNodeSelf: Float32Array, rootTotalSummary: number, sortedRoots: IndexIntoFuncTable[], - totalPerRootNode: Map, - rootNodesWithChildren: Set, + totalPerRootFunc: Float32Array, + hasChildrenPerRootFunc: Uint8Array, |}; export type CallTreeTimings = @@ -185,8 +185,8 @@ class CallTreeInternalInverted implements CallTreeInternal { _callNodeSelf: Float32Array; _rootNodes: IndexIntoCallNodeTable[]; _funcCount: number; - _totalPerRootNode: Map; - _rootNodesWithChildren: Set; + _totalPerRootFunc: Float32Array; + _hasChildrenPerRootFunc: Uint8Array; _totalAndHasChildrenPerNonRootNode: Map< IndexIntoCallNodeTable, TotalAndHasChildren, @@ -199,10 +199,10 @@ class CallTreeInternalInverted implements CallTreeInternal { this._callNodeInfo = callNodeInfo; this._nonInvertedCallNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); this._callNodeSelf = callTreeTimingsInverted.callNodeSelf; - const { sortedRoots, totalPerRootNode, rootNodesWithChildren } = + const { sortedRoots, totalPerRootFunc, hasChildrenPerRootFunc } = callTreeTimingsInverted; - this._totalPerRootNode = totalPerRootNode; - this._rootNodesWithChildren = rootNodesWithChildren; + this._totalPerRootFunc = totalPerRootFunc; + this._hasChildrenPerRootFunc = hasChildrenPerRootFunc; this._rootNodes = sortedRoots; } @@ -212,7 +212,7 @@ class CallTreeInternalInverted implements CallTreeInternal { hasChildren(callNodeIndex: IndexIntoCallNodeTable): boolean { if (this._callNodeInfo.isRoot(callNodeIndex)) { - return this._rootNodesWithChildren.has(callNodeIndex); + return this._hasChildrenPerRootFunc[callNodeIndex] !== 0; } return this._getTotalAndHasChildren(callNodeIndex).hasChildren; } @@ -238,7 +238,7 @@ class CallTreeInternalInverted implements CallTreeInternal { getSelfAndTotal(callNodeIndex: IndexIntoCallNodeTable): SelfAndTotal { if (this._callNodeInfo.isRoot(callNodeIndex)) { - const total = ensureExists(this._totalPerRootNode.get(callNodeIndex)); + const total = this._totalPerRootFunc[callNodeIndex]; return { self: total, total }; } const { total } = this._getTotalAndHasChildren(callNodeIndex); @@ -643,9 +643,9 @@ export function getSelfAndTotalForCallNode( case 'INVERTED': { const callNodeInfoInverted = ensureExists(callNodeInfo.asInverted()); const { timings } = callTreeTimings; - const { callNodeSelf, totalPerRootNode } = timings; + const { callNodeSelf, totalPerRootFunc } = timings; if (callNodeInfoInverted.isRoot(callNodeIndex)) { - const total = totalPerRootNode.get(callNodeIndex) ?? 0; + const total = totalPerRootFunc[callNodeIndex]; return { self: total, total }; } const { total } = _getInvertedTreeNodeTotalAndHasChildren( @@ -693,13 +693,14 @@ export function computeCallTreeTimingsInverted( callNodeInfo: CallNodeInfoInverted, { callNodeSelf, rootTotalSummary }: CallNodeSelfAndSummary ): CallTreeTimingsInverted { - const roots = callNodeInfo.getRoots(); + const funcCount = callNodeInfo.getFuncCount(); const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); const callNodeTableFuncCol = callNodeTable.func; const callNodeTableDepthCol = callNodeTable.depth; - const totalPerRootNode = new Map(); - const rootNodesWithChildren = new Set(); - const seenRoots = new Set(); + const totalPerRootFunc = new Float32Array(funcCount); + const hasChildrenPerRootFunc = new Uint8Array(funcCount); + const seenPerRootFunc = new Uint8Array(funcCount); + const sortedRoots = []; for (let i = 0; i < callNodeSelf.length; i++) { const self = callNodeSelf[i]; if (self === 0) { @@ -710,36 +711,25 @@ export function computeCallTreeTimingsInverted( // call tree. This is done by finding the inverted root which corresponds to // the self function of the non-inverted call node. const func = callNodeTableFuncCol[i]; - const rootNode = roots.find( - (invertedCallNode) => callNodeInfo.funcForNode(invertedCallNode) === func - ); - if (rootNode === undefined) { - throw new Error( - "Couldn't find the inverted root for a function with non-zero self time." - ); - } - totalPerRootNode.set( - rootNode, - (totalPerRootNode.get(rootNode) ?? 0) + self - ); - seenRoots.add(rootNode); + totalPerRootFunc[func] += self; + if (seenPerRootFunc[func] === 0) { + seenPerRootFunc[func] = 1; + sortedRoots.push(func); + } if (callNodeTableDepthCol[i] !== 0) { - rootNodesWithChildren.add(rootNode); + hasChildrenPerRootFunc[func] = 1; } } - const sortedRoots = [...seenRoots]; sortedRoots.sort( - (a, b) => - Math.abs(totalPerRootNode.get(b) ?? 0) - - Math.abs(totalPerRootNode.get(a) ?? 0) + (a, b) => Math.abs(totalPerRootFunc[b]) - Math.abs(totalPerRootFunc[a]) ); return { callNodeSelf, rootTotalSummary, sortedRoots, - totalPerRootNode, - rootNodesWithChildren, + totalPerRootFunc, + hasChildrenPerRootFunc, }; } diff --git a/src/types/profile-derived.js b/src/types/profile-derived.js index eabe3d0dbe..2d168c7aa2 100644 --- a/src/types/profile-derived.js +++ b/src/types/profile-derived.js @@ -162,9 +162,6 @@ export interface CallNodeInfo { func: IndexIntoFuncTable ): IndexIntoCallNodeTable | null; - // Returns the list of root nodes. - getRoots(): IndexIntoCallNodeTable[]; - // Returns whether the given node is a root node. isRoot(callNodeIndex: IndexIntoCallNodeTable): boolean; @@ -283,17 +280,17 @@ export type SuffixOrderIndex = number; * * ``` * Represents call paths ending in - * - [in0] A (so:0..3) = A = ... A (cn0, cn4, cn2) - * - [in1] A (so:1..2) = A <- A = ... A -> A (cn4) - * - [in2] B (so:2..3) = A <- B = ... B -> A (cn2) - * - [in3] A (so:2..3) = A <- B <- A = ... A -> B -> A (cn2) - * - [in4] B (so:3..5) = B = ... B (cn1, cn5) - * - [in5] A (so:3..5) = B <- A = ... A -> B (cn1, cn5) - * - [in6] A (so:4..5) = B <- A <- A = ... A -> A -> B (cn5) - * - [in7] C (so:5..7) = C = ... C (cn6, cn3) - * - [in8] A (so:5..6) = C <- A = ... A -> C (cn6) - * - [in9] B (so:6..7) = C <- B = ... B -> C (cn3) - * - [in10] A (so:6..7) = C <- B <- A = ... A -> B -> C (cn3) + * - [in0] A (so:0..3) = A = ... A (cn0, cn4, cn2) + * - [in3] A (so:1..2) = A <- A = ... A -> A (cn4) + * - [in4] B (so:2..3) = A <- B = ... B -> A (cn2) + * - [in6] A (so:2..3) = A <- B <- A = ... A -> B -> A (cn2) + * - [in1] B (so:3..5) = B = ... B (cn1, cn5) + * - [in5] A (so:3..5) = B <- A = ... A -> B (cn1, cn5) + * - [in7] A (so:4..5) = B <- A <- A = ... A -> A -> B (cn5) + * - [in2] C (so:5..7) = C = ... C (cn6, cn3) + * - [in8] A (so:5..6) = C <- A = ... A -> C (cn6) + * - [in9] B (so:6..7) = C <- B = ... B -> C (cn3) + * - [in10] A (so:6..7) = C <- B <- A = ... A -> B -> C (cn3) * ``` * * In the suffix order, call paths become grouped in such a way that call paths @@ -308,7 +305,7 @@ export type SuffixOrderIndex = number; * order index range 3..5. * * Suffix ordered call nodes: [0, 4, 2, 1, 5, 6, 3] (soX -> cnY) - * Suffix order indexes: [0, 3, 2, 6, 1, 4, 5] (cnX -> soY) + * Suffix order indexes: [0, 3, 2, 6, 1, 4, 5] (cnX -> soY) * * cnX: Non-inverted call node index X * soX: Suffix order index X @@ -316,6 +313,11 @@ export type SuffixOrderIndex = number; * so:X..Y: Suffix order index range soX..soY (soY excluded) */ export interface CallNodeInfoInverted extends CallNodeInfo { + // Get the number of functions. There is one root per function. + // So this is also the number of roots at the same time. + // The inverted call node index for a root is the same as the function index. + getFuncCount(): number; + // Get a mapping SuffixOrderIndex -> IndexIntoNonInvertedCallNodeTable. // This array contains all non-inverted call node indexes, ordered by // call path suffix. See "suffix order" in the documentation above.