Skip to content

Commit

Permalink
Create inverted call nodes lazily.
Browse files Browse the repository at this point in the history
  • Loading branch information
mstange committed Aug 7, 2024
1 parent 42f41a9 commit b18ef2d
Show file tree
Hide file tree
Showing 11 changed files with 800 additions and 656 deletions.
775 changes: 714 additions & 61 deletions src/profile-logic/call-node-info.js

Large diffs are not rendered by default.

176 changes: 47 additions & 129 deletions src/profile-logic/profile-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import {
shallowCloneFrameTable,
shallowCloneFuncTable,
} from './data-structures';
import { CallNodeInfoImpl, CallNodeInfoInvertedImpl } from './call-node-info';
import {
CallNodeInfoNonInvertedImpl,
CallNodeInfoInvertedImpl,
} from './call-node-info';
import {
INSTANT,
INTERVAL,
Expand Down Expand Up @@ -107,8 +110,7 @@ export function getCallNodeInfo(
funcTable,
defaultCategory
);
return new CallNodeInfoImpl(
callNodeTable,
return new CallNodeInfoNonInvertedImpl(
callNodeTable,
stackIndexToCallNodeIndex
);
Expand Down Expand Up @@ -428,48 +430,56 @@ function _createCallNodeTableFromUnorderedComponents(
* Generate the inverted CallNodeInfo for a thread.
*/
export function getInvertedCallNodeInfo(
thread: Thread,
nonInvertedCallNodeTable: CallNodeTable,
stackIndexToNonInvertedCallNodeIndex: Int32Array,
defaultCategory: IndexIntoCategoryList
defaultCategory: IndexIntoCategoryList,
funcCount: number
): CallNodeInfoInverted {
// We compute an inverted stack table, but we don't let it escape this function.
const { invertedThread } = _computeThreadWithInvertedStackTable(
thread,
defaultCategory
);

// Create an inverted call node table based on the inverted stack table.
const { callNodeTable } = computeCallNodeTable(
invertedThread.stackTable,
invertedThread.frameTable,
invertedThread.funcTable,
defaultCategory
);

// TEMPORARY: Compute a suffix order for the entire non-inverted call node table.
// See the CallNodeInfoInverted interface for more details about the suffix order.
// By the end of this commit stack, the suffix order will be computed incrementally
// as inverted nodes are created; we won't compute the entire order upfront.
const nonInvertedCallNodeCount = nonInvertedCallNodeTable.length;
const suffixOrderedCallNodes = new Uint32Array(nonInvertedCallNodeCount);
const suffixOrderIndexes = new Uint32Array(nonInvertedCallNodeCount);
for (let i = 0; i < nonInvertedCallNodeCount; i++) {
suffixOrderedCallNodes[i] = i;
}
suffixOrderedCallNodes.sort((a, b) =>
_compareNonInvertedCallNodesInSuffixOrder(a, b, nonInvertedCallNodeTable)
);
for (let i = 0; i < suffixOrderedCallNodes.length; i++) {
suffixOrderIndexes[suffixOrderedCallNodes[i]] = i;
}
const callNodeCount = nonInvertedCallNodeTable.length;
const suffixOrderedCallNodes = new Uint32Array(callNodeCount);
const suffixOrderingIndexes = new Uint32Array(callNodeCount);
const callNodeTableFuncCol = nonInvertedCallNodeTable.func;

// Compute, per func, how many non-inverted call nodes end in this func
const nextIndexPerFunc = new Uint32Array(funcCount);
for (let i = 0; i < callNodeTableFuncCol.length; i++) {
const func = callNodeTableFuncCol[i];
nextIndexPerFunc[func]++;
}
// Compute cumulative start index
let previousEndIndex = 0;
for (let func = 0; func < nextIndexPerFunc.length; func++) {
const count = nextIndexPerFunc[func];
nextIndexPerFunc[func] = previousEndIndex;
previousEndIndex += count;
}
// Compute a first pass of the suffix ordering. This is just good enough for
// the roots of the inverted tree. This sort will be refined by
// CallNodeInfoInvertedImpl as more inverted nodes are created on-demand.
for (let i = 0; i < callNodeTableFuncCol.length; i++) {
const func = callNodeTableFuncCol[i];
const nextIndex = nextIndexPerFunc[func]++;
suffixOrderedCallNodes[nextIndex] = i;
suffixOrderingIndexes[i] = nextIndex;
}
// Compute the suffix order index ranges for each root. We only compute the
// end of each range because the start is implied by the end of the previous
// inverted node (or 0 for the first inverted node).
const rootOrderingIndexRangeEndCol = new Uint32Array(funcCount);
for (let func = 0; func < funcCount - 1; func++) {
const endIndex = nextIndexPerFunc[func];
rootOrderingIndexRangeEndCol[func] = endIndex;
}
rootOrderingIndexRangeEndCol[funcCount - 1] = suffixOrderedCallNodes.length;

return new CallNodeInfoInvertedImpl(
callNodeTable,
nonInvertedCallNodeTable,
stackIndexToNonInvertedCallNodeIndex,
suffixOrderedCallNodes,
suffixOrderIndexes
suffixOrderingIndexes,
rootOrderingIndexRangeEndCol,
defaultCategory,
nextIndexPerFunc
);
}

Expand Down Expand Up @@ -2156,98 +2166,6 @@ export function computeCallNodeMaxDepthPlusOne(
return maxDepth + 1;
}

function _computeThreadWithInvertedStackTable(
thread: Thread,
defaultCategory: IndexIntoCategoryList
): {
invertedThread: Thread,
oldStackToNewStack: Map<IndexIntoStackTable, IndexIntoStackTable>,
} {
return timeCode('_computeThreadWithInvertedStackTable', () => {
const { stackTable, frameTable } = thread;

const newStackTable = {
length: 0,
frame: [],
category: [],
subcategory: [],
prefix: [],
};
// Create a Map that keys off of two values, both the prefix and frame combination
// by using a bit of math: prefix * frameCount + frame => stackIndex
const prefixAndFrameToStack = new Map();
const frameCount = frameTable.length;

// Returns the stackIndex for a specific frame (that is, a function and its
// context), and a specific prefix. If it doesn't exist yet it will create
// a new stack entry and return its index.
function stackFor(prefix, frame, category, subcategory) {
const prefixAndFrameIndex =
(prefix === null ? -1 : prefix) * frameCount + frame;
let stackIndex = prefixAndFrameToStack.get(prefixAndFrameIndex);
if (stackIndex === undefined) {
stackIndex = newStackTable.length++;
newStackTable.prefix[stackIndex] = prefix;
newStackTable.frame[stackIndex] = frame;
newStackTable.category[stackIndex] = category;
newStackTable.subcategory[stackIndex] = subcategory;
prefixAndFrameToStack.set(prefixAndFrameIndex, stackIndex);
} else if (newStackTable.category[stackIndex] !== category) {
// If two stack nodes from the non-inverted stack tree with different
// categories happen to collapse into the same stack node in the
// inverted tree, discard their category and set the category to the
// default category.
newStackTable.category[stackIndex] = defaultCategory;
newStackTable.subcategory[stackIndex] = 0;
} else if (newStackTable.subcategory[stackIndex] !== subcategory) {
// If two stack nodes from the non-inverted stack tree with the same
// category but different subcategories happen to collapse into the same
// stack node in the inverted tree, discard their subcategory and set it
// to the "Other" subcategory.
newStackTable.subcategory[stackIndex] = 0;
}
return stackIndex;
}

const oldStackToNewStack = new Map();

// For one specific stack, this will ensure that stacks are created for all
// of its ancestors, by walking its prefix chain up to the root.
function convertStack(stackIndex) {
if (stackIndex === null) {
return null;
}
let newStack = oldStackToNewStack.get(stackIndex);
if (newStack === undefined) {
newStack = null;
for (
let currentStack = stackIndex;
currentStack !== null;
currentStack = stackTable.prefix[currentStack]
) {
// Notice how we reuse the previous stack as the prefix. This is what
// effectively inverts the call tree.
newStack = stackFor(
newStack,
stackTable.frame[currentStack],
stackTable.category[currentStack],
stackTable.subcategory[currentStack]
);
}
oldStackToNewStack.set(stackIndex, ensureExists(newStack));
}
return newStack;
}

const invertedThread = updateThreadStacks(
thread,
newStackTable,
convertStack
);
return { invertedThread, oldStackToNewStack };
});
}

/**
* Sometimes we want to update the stacks for a thread, for instance while searching
* for a text string, or doing a call tree transformation. This function abstracts
Expand Down
8 changes: 4 additions & 4 deletions src/selectors/per-thread/stack-sample.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,15 @@ export function getStackAndSampleSelectorsPerThread(

const _getInvertedCallNodeInfo: Selector<CallNodeInfo> =
createSelectorWithTwoCacheSlots(
threadSelectors.getFilteredThread,
_getNonInvertedCallNodeInfo,
ProfileSelectors.getDefaultCategory,
(thread, nonInvertedCallNodeInfo, defaultCategory) => {
(state) => threadSelectors.getFilteredThread(state).funcTable.length,
(nonInvertedCallNodeInfo, defaultCategory, funcCount) => {
return ProfileData.getInvertedCallNodeInfo(
thread,
nonInvertedCallNodeInfo.getNonInvertedCallNodeTable(),
nonInvertedCallNodeInfo.getStackIndexToNonInvertedCallNodeIndex(),
defaultCategory
defaultCategory,
funcCount
);
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4374,7 +4374,7 @@ for understanding where time was actually spent in a program."
class="react-contextmenu-wrapper treeViewContextMenu"
>
<div
aria-activedescendant="treeViewRow-9"
aria-activedescendant="treeViewRow-13"
aria-label="Call tree"
class="treeViewBody"
role="tree"
Expand Down Expand Up @@ -4721,7 +4721,7 @@ for understanding where time was actually spent in a program."
aria-level="1"
aria-selected="false"
class="treeViewRow treeViewRowScrolledColumns even"
id="treeViewRow-5"
id="treeViewRow-7"
role="treeitem"
style="height: 16px; line-height: 16px;"
>
Expand Down Expand Up @@ -4751,7 +4751,7 @@ for understanding where time was actually spent in a program."
aria-level="2"
aria-selected="false"
class="treeViewRow treeViewRowScrolledColumns odd"
id="treeViewRow-6"
id="treeViewRow-9"
role="treeitem"
style="height: 16px; line-height: 16px;"
>
Expand Down Expand Up @@ -4781,7 +4781,7 @@ for understanding where time was actually spent in a program."
aria-level="3"
aria-selected="false"
class="treeViewRow treeViewRowScrolledColumns even"
id="treeViewRow-7"
id="treeViewRow-10"
role="treeitem"
style="height: 16px; line-height: 16px;"
>
Expand Down Expand Up @@ -4813,7 +4813,7 @@ for understanding where time was actually spent in a program."
aria-level="4"
aria-selected="false"
class="treeViewRow treeViewRowScrolledColumns odd"
id="treeViewRow-8"
id="treeViewRow-11"
role="treeitem"
style="height: 16px; line-height: 16px;"
>
Expand Down Expand Up @@ -4842,7 +4842,7 @@ for understanding where time was actually spent in a program."
aria-level="5"
aria-selected="true"
class="treeViewRow treeViewRowScrolledColumns even isSelected"
id="treeViewRow-9"
id="treeViewRow-13"
role="treeitem"
style="height: 16px; line-height: 16px;"
>
Expand Down Expand Up @@ -4872,7 +4872,7 @@ for understanding where time was actually spent in a program."
aria-level="4"
aria-selected="false"
class="treeViewRow treeViewRowScrolledColumns odd"
id="treeViewRow-10"
id="treeViewRow-12"
role="treeitem"
style="height: 16px; line-height: 16px;"
>
Expand Down Expand Up @@ -4902,7 +4902,7 @@ for understanding where time was actually spent in a program."
aria-level="1"
aria-selected="false"
class="treeViewRow treeViewRowScrolledColumns even"
id="treeViewRow-0"
id="treeViewRow-4"
role="treeitem"
style="height: 16px; line-height: 16px;"
>
Expand Down
Loading

0 comments on commit b18ef2d

Please sign in to comment.