Skip to content

Commit 1dc1d54

Browse files
committed
Optimize handling of roots.
The new structure gives us a nice guarantee about roots of the inverted tree: There is an inverted root for every func, and their indexes are identical. This makes it really cheap to translate between the call node index and the func index (no conversion or lookup is necessary) and also makes it cheap to check if a node is a root. This commit also replaces a few maps and sets with typed arrays for performance. This is easier now that the root indexes are all contiguous.
1 parent b18ef2d commit 1dc1d54

File tree

3 files changed

+53
-67
lines changed

3 files changed

+53
-67
lines changed

src/profile-logic/call-node-info.js

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -449,13 +449,16 @@ export class CallNodeInfoInvertedImpl implements CallNodeInfoInverted {
449449
// The mapping of non-inverted stack index to non-inverted call node index.
450450
_stackIndexToNonInvertedCallNodeIndex: Int32Array;
451451

452-
// The number of roots, i.e. this._roots.length.
452+
// The number of roots, which is also the number of functions. Each root of
453+
// the inverted tree represents a "self" function, i.e. all call paths which
454+
// end in a certain function.
455+
// We have roots even for functions which aren't used as "self" functions in
456+
// any sampled stacks, for simplicity. The actual displayed number of roots
457+
// in the call tree will usually be lower because roots with a zero total sample
458+
// count will be filtered out. But any data in this class is fully independent
459+
// from sample counts.
453460
_rootCount: number;
454461

455-
// All inverted call tree roots. The roots of the inverted call tree are the
456-
// "self" functions of the non-inverted call paths.
457-
_roots: InvertedCallNodeHandle[];
458-
459462
// This is a Map<SuffixOrderIndex, IndexIntoNonInvertedCallNodeTable>.
460463
// It lists the non-inverted call nodes in "suffix order", i.e. ordered by
461464
// comparing their call paths from back to front.
@@ -497,16 +500,7 @@ export class CallNodeInfoInvertedImpl implements CallNodeInfoInverted {
497500
this._suffixOrderedCallNodes = suffixOrderedCallNodes;
498501
this._suffixOrderIndexes = suffixOrderIndexes;
499502
this._defaultCategory = defaultCategory;
500-
501-
const rootCount = rootSuffixOrderIndexRangeEndCol.length;
502-
this._rootCount = rootCount;
503-
504-
const roots = new Array(rootCount);
505-
for (let i = 0; i < rootCount; i++) {
506-
roots[i] = i;
507-
}
508-
this._roots = roots;
509-
503+
this._rootCount = rootSuffixOrderIndexRangeEndCol.length;
510504
this._funcCountBuf = funcCountBuf;
511505
this._funcCountBuf.fill(0);
512506
const invertedRootCallNodeTable = _createInvertedRootCallNodeTable(
@@ -544,8 +538,8 @@ export class CallNodeInfoInvertedImpl implements CallNodeInfoInverted {
544538
return this._suffixOrderIndexes;
545539
}
546540

547-
getRoots(): Array<InvertedCallNodeHandle> {
548-
return this._roots;
541+
getFuncCount(): number {
542+
return this._rootCount;
549543
}
550544

551545
isRoot(nodeHandle: InvertedCallNodeHandle): boolean {

src/profile-logic/call-tree.js

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ export type CallTreeTimingsInverted = {|
5858
callNodeSelf: Float32Array,
5959
rootTotalSummary: number,
6060
sortedRoots: IndexIntoFuncTable[],
61-
totalPerRootNode: Map<IndexIntoCallNodeTable, number>,
62-
rootNodesWithChildren: Set<IndexIntoCallNodeTable>,
61+
totalPerRootFunc: Float32Array,
62+
hasChildrenPerRootFunc: Uint8Array,
6363
|};
6464

6565
export type CallTreeTimings =
@@ -185,8 +185,8 @@ class CallTreeInternalInverted implements CallTreeInternal {
185185
_callNodeSelf: Float32Array;
186186
_rootNodes: IndexIntoCallNodeTable[];
187187
_funcCount: number;
188-
_totalPerRootNode: Map<IndexIntoCallNodeTable, number>;
189-
_rootNodesWithChildren: Set<IndexIntoCallNodeTable>;
188+
_totalPerRootFunc: Float32Array;
189+
_hasChildrenPerRootFunc: Uint8Array;
190190
_totalAndHasChildrenPerNonRootNode: Map<
191191
IndexIntoCallNodeTable,
192192
TotalAndHasChildren,
@@ -199,10 +199,10 @@ class CallTreeInternalInverted implements CallTreeInternal {
199199
this._callNodeInfo = callNodeInfo;
200200
this._nonInvertedCallNodeTable = callNodeInfo.getNonInvertedCallNodeTable();
201201
this._callNodeSelf = callTreeTimingsInverted.callNodeSelf;
202-
const { sortedRoots, totalPerRootNode, rootNodesWithChildren } =
202+
const { sortedRoots, totalPerRootFunc, hasChildrenPerRootFunc } =
203203
callTreeTimingsInverted;
204-
this._totalPerRootNode = totalPerRootNode;
205-
this._rootNodesWithChildren = rootNodesWithChildren;
204+
this._totalPerRootFunc = totalPerRootFunc;
205+
this._hasChildrenPerRootFunc = hasChildrenPerRootFunc;
206206
this._rootNodes = sortedRoots;
207207
}
208208

@@ -212,7 +212,7 @@ class CallTreeInternalInverted implements CallTreeInternal {
212212

213213
hasChildren(callNodeIndex: IndexIntoCallNodeTable): boolean {
214214
if (this._callNodeInfo.isRoot(callNodeIndex)) {
215-
return this._rootNodesWithChildren.has(callNodeIndex);
215+
return this._hasChildrenPerRootFunc[callNodeIndex] !== 0;
216216
}
217217
return this._getTotalAndHasChildren(callNodeIndex).hasChildren;
218218
}
@@ -238,7 +238,7 @@ class CallTreeInternalInverted implements CallTreeInternal {
238238

239239
getSelfAndTotal(callNodeIndex: IndexIntoCallNodeTable): SelfAndTotal {
240240
if (this._callNodeInfo.isRoot(callNodeIndex)) {
241-
const total = ensureExists(this._totalPerRootNode.get(callNodeIndex));
241+
const total = this._totalPerRootFunc[callNodeIndex];
242242
return { self: total, total };
243243
}
244244
const { total } = this._getTotalAndHasChildren(callNodeIndex);
@@ -643,9 +643,9 @@ export function getSelfAndTotalForCallNode(
643643
case 'INVERTED': {
644644
const callNodeInfoInverted = ensureExists(callNodeInfo.asInverted());
645645
const { timings } = callTreeTimings;
646-
const { callNodeSelf, totalPerRootNode } = timings;
646+
const { callNodeSelf, totalPerRootFunc } = timings;
647647
if (callNodeInfoInverted.isRoot(callNodeIndex)) {
648-
const total = totalPerRootNode.get(callNodeIndex) ?? 0;
648+
const total = totalPerRootFunc[callNodeIndex];
649649
return { self: total, total };
650650
}
651651
const { total } = _getInvertedTreeNodeTotalAndHasChildren(
@@ -693,13 +693,14 @@ export function computeCallTreeTimingsInverted(
693693
callNodeInfo: CallNodeInfoInverted,
694694
{ callNodeSelf, rootTotalSummary }: CallNodeSelfAndSummary
695695
): CallTreeTimingsInverted {
696-
const roots = callNodeInfo.getRoots();
696+
const funcCount = callNodeInfo.getFuncCount();
697697
const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable();
698698
const callNodeTableFuncCol = callNodeTable.func;
699699
const callNodeTableDepthCol = callNodeTable.depth;
700-
const totalPerRootNode = new Map();
701-
const rootNodesWithChildren = new Set();
702-
const seenRoots = new Set();
700+
const totalPerRootFunc = new Float32Array(funcCount);
701+
const hasChildrenPerRootFunc = new Uint8Array(funcCount);
702+
const seenPerRootFunc = new Uint8Array(funcCount);
703+
const sortedRoots = [];
703704
for (let i = 0; i < callNodeSelf.length; i++) {
704705
const self = callNodeSelf[i];
705706
if (self === 0) {
@@ -710,36 +711,25 @@ export function computeCallTreeTimingsInverted(
710711
// call tree. This is done by finding the inverted root which corresponds to
711712
// the self function of the non-inverted call node.
712713
const func = callNodeTableFuncCol[i];
713-
const rootNode = roots.find(
714-
(invertedCallNode) => callNodeInfo.funcForNode(invertedCallNode) === func
715-
);
716-
if (rootNode === undefined) {
717-
throw new Error(
718-
"Couldn't find the inverted root for a function with non-zero self time."
719-
);
720-
}
721714

722-
totalPerRootNode.set(
723-
rootNode,
724-
(totalPerRootNode.get(rootNode) ?? 0) + self
725-
);
726-
seenRoots.add(rootNode);
715+
totalPerRootFunc[func] += self;
716+
if (seenPerRootFunc[func] === 0) {
717+
seenPerRootFunc[func] = 1;
718+
sortedRoots.push(func);
719+
}
727720
if (callNodeTableDepthCol[i] !== 0) {
728-
rootNodesWithChildren.add(rootNode);
721+
hasChildrenPerRootFunc[func] = 1;
729722
}
730723
}
731-
const sortedRoots = [...seenRoots];
732724
sortedRoots.sort(
733-
(a, b) =>
734-
Math.abs(totalPerRootNode.get(b) ?? 0) -
735-
Math.abs(totalPerRootNode.get(a) ?? 0)
725+
(a, b) => Math.abs(totalPerRootFunc[b]) - Math.abs(totalPerRootFunc[a])
736726
);
737727
return {
738728
callNodeSelf,
739729
rootTotalSummary,
740730
sortedRoots,
741-
totalPerRootNode,
742-
rootNodesWithChildren,
731+
totalPerRootFunc,
732+
hasChildrenPerRootFunc,
743733
};
744734
}
745735

src/types/profile-derived.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,6 @@ export interface CallNodeInfo {
162162
func: IndexIntoFuncTable
163163
): IndexIntoCallNodeTable | null;
164164

165-
// Returns the list of root nodes.
166-
getRoots(): IndexIntoCallNodeTable[];
167-
168165
// Returns whether the given node is a root node.
169166
isRoot(callNodeIndex: IndexIntoCallNodeTable): boolean;
170167

@@ -283,17 +280,17 @@ export type SuffixOrderIndex = number;
283280
*
284281
* ```
285282
* Represents call paths ending in
286-
* - [in0] A (so:0..3) = A = ... A (cn0, cn4, cn2)
287-
* - [in1] A (so:1..2) = A <- A = ... A -> A (cn4)
288-
* - [in2] B (so:2..3) = A <- B = ... B -> A (cn2)
289-
* - [in3] A (so:2..3) = A <- B <- A = ... A -> B -> A (cn2)
290-
* - [in4] B (so:3..5) = B = ... B (cn1, cn5)
291-
* - [in5] A (so:3..5) = B <- A = ... A -> B (cn1, cn5)
292-
* - [in6] A (so:4..5) = B <- A <- A = ... A -> A -> B (cn5)
293-
* - [in7] C (so:5..7) = C = ... C (cn6, cn3)
294-
* - [in8] A (so:5..6) = C <- A = ... A -> C (cn6)
295-
* - [in9] B (so:6..7) = C <- B = ... B -> C (cn3)
296-
* - [in10] A (so:6..7) = C <- B <- A = ... A -> B -> C (cn3)
283+
* - [in0] A (so:0..3) = A = ... A (cn0, cn4, cn2)
284+
* - [in3] A (so:1..2) = A <- A = ... A -> A (cn4)
285+
* - [in4] B (so:2..3) = A <- B = ... B -> A (cn2)
286+
* - [in6] A (so:2..3) = A <- B <- A = ... A -> B -> A (cn2)
287+
* - [in1] B (so:3..5) = B = ... B (cn1, cn5)
288+
* - [in5] A (so:3..5) = B <- A = ... A -> B (cn1, cn5)
289+
* - [in7] A (so:4..5) = B <- A <- A = ... A -> A -> B (cn5)
290+
* - [in2] C (so:5..7) = C = ... C (cn6, cn3)
291+
* - [in8] A (so:5..6) = C <- A = ... A -> C (cn6)
292+
* - [in9] B (so:6..7) = C <- B = ... B -> C (cn3)
293+
* - [in10] A (so:6..7) = C <- B <- A = ... A -> B -> C (cn3)
297294
* ```
298295
*
299296
* In the suffix order, call paths become grouped in such a way that call paths
@@ -308,14 +305,19 @@ export type SuffixOrderIndex = number;
308305
* order index range 3..5.
309306
*
310307
* Suffix ordered call nodes: [0, 4, 2, 1, 5, 6, 3] (soX -> cnY)
311-
* Suffix order indexes: [0, 3, 2, 6, 1, 4, 5] (cnX -> soY)
308+
* Suffix order indexes: [0, 3, 2, 6, 1, 4, 5] (cnX -> soY)
312309
*
313310
* cnX: Non-inverted call node index X
314311
* soX: Suffix order index X
315312
* inX: Inverted call node index X
316313
* so:X..Y: Suffix order index range soX..soY (soY excluded)
317314
*/
318315
export interface CallNodeInfoInverted extends CallNodeInfo {
316+
// Get the number of functions. There is one root per function.
317+
// So this is also the number of roots at the same time.
318+
// The inverted call node index for a root is the same as the function index.
319+
getFuncCount(): number;
320+
319321
// Get a mapping SuffixOrderIndex -> IndexIntoNonInvertedCallNodeTable.
320322
// This array contains all non-inverted call node indexes, ordered by
321323
// call path suffix. See "suffix order" in the documentation above.

0 commit comments

Comments
 (0)