Skip to content

Commit bc32214

Browse files
committed
Change mergeCallNode to only create a new prefix column, like mergeFunction.
1 parent 938af54 commit bc32214

1 file changed

Lines changed: 54 additions & 79 deletions

File tree

src/profile-logic/transforms.ts

Lines changed: 54 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
toValidImplementationFilter,
1111
updateThreadStacks,
1212
updateThreadStacksByGeneratingNewStackColumns,
13-
getMapStackUpdater,
1413
getOriginAnnotationForFunc,
1514
createStackTableBySkippingDiscarded,
1615
applyTransformOutputToThread,
@@ -759,102 +758,78 @@ export function mergeCallNode(
759758
): Thread {
760759
return timeCode('mergeCallNode', () => {
761760
const { stackTable, frameTable } = thread;
762-
// Depth here is 0 indexed.
763-
const depthAtCallNodePathLeaf = callNodePath.length - 1;
764-
const oldStackToNewStack: Map<
761+
762+
// Maps merged stacks to their effective parent (the stack that samples pointing
763+
// to the merged stack should be attributed to). Only contains entries for merged
764+
// stacks; the vast majority of stacks are not merged and map to themselves.
765+
const mergedStackToEffectiveParent = new Map<
765766
IndexIntoStackTable | null,
766767
IndexIntoStackTable | null
767-
> = new Map();
768-
// A root stack's prefix will be null. Maintain that relationship from old to new
769-
// stacks by mapping from null to null.
770-
oldStackToNewStack.set(null, null);
771-
const newFrameCol = [];
772-
const newPrefixCol = [];
773-
const newCategoryCol = [];
774-
const newSubcategoryCol = [];
775-
let newLength = 0;
776-
// Provide two arrays to efficiently cache values for the algorithm. This probably
777-
// could be refactored to use only one array here.
778-
const stackDepths = [];
779-
const stackMatches = [];
768+
>();
769+
const newPrefixCol = new Array<IndexIntoStackTable | null>(
770+
stackTable.length
771+
);
772+
780773
const funcMatchesImplementation = FUNC_MATCHES[implementation];
774+
775+
const callNodePathLength = callNodePath.length;
776+
// A map to keep track of whether a stack matches (part of) the call node path.
777+
// If undefined: no match
778+
// Otherwise: length of the partial match, including this stack
779+
// All values are < callNodePathLength.
780+
const partialMatchLengthAtStack = new Map<
781+
IndexIntoStackTable | null,
782+
number
783+
>();
784+
partialMatchLengthAtStack.set(null, 0);
781785
for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) {
782786
const prefix = stackTable.prefix[stackIndex];
787+
788+
// If our prefix got merged away, remap it to its parent.
789+
const parentOfPrefix = mergedStackToEffectiveParent.get(prefix);
790+
const effectivePrefix =
791+
parentOfPrefix !== undefined ? parentOfPrefix : prefix;
792+
newPrefixCol[stackIndex] = effectivePrefix;
793+
794+
const prefixPartialMatchLength = partialMatchLengthAtStack.get(prefix);
795+
if (prefixPartialMatchLength === undefined) {
796+
// No match, nothing else to do here
797+
continue;
798+
}
799+
800+
// Now we know that this stack's prefix was a (partial) match for our CallNodePath.
783801
const frameIndex = stackTable.frame[stackIndex];
784-
const category = stackTable.category[stackIndex];
785-
const subcategory = stackTable.subcategory[stackIndex];
786802
const funcIndex = frameTable.func[frameIndex];
787803

788-
const doesPrefixMatch = prefix === null ? true : stackMatches[prefix];
789-
const prefixDepth: number = prefix === null ? -1 : stackDepths[prefix];
790-
const currentFuncOnPath = callNodePath[prefixDepth + 1];
791-
792-
let doMerge = false;
793-
let stackDepth: number = prefixDepth;
794-
let doesMatchCallNodePath;
795-
if (doesPrefixMatch && stackDepth < depthAtCallNodePathLeaf) {
796-
// This stack's prefixes were in our CallNodePath.
797-
if (currentFuncOnPath === funcIndex) {
798-
// This stack's function matches too!
799-
doesMatchCallNodePath = true;
800-
if (stackDepth + 1 === depthAtCallNodePathLeaf) {
801-
// Holy cow, we found a match for our merge operation and can merge this stack.
802-
doMerge = true;
803-
} else {
804-
// Since we found a match, increase the stack depth. This should match
805-
// the depth of the implementation filtered stacks.
806-
stackDepth++;
807-
}
808-
} else if (!funcMatchesImplementation(thread, funcIndex)) {
809-
// This stack's function does not match the CallNodePath, however it's not part
810-
// of the CallNodePath's implementation filter. Go ahead and keep it.
811-
doesMatchCallNodePath = true;
804+
if (funcIndex === callNodePath[prefixPartialMatchLength]) {
805+
// This stack's function matches too!
806+
const matchLength = prefixPartialMatchLength + 1;
807+
if (matchLength === callNodePathLength) {
808+
// The entire path matched and we found a node that needs to be merged away.
809+
mergedStackToEffectiveParent.set(stackIndex, effectivePrefix);
812810
} else {
813-
// While all of the predecessors matched, this stack's function does not :(
814-
doesMatchCallNodePath = false;
811+
// Not reached the end yet, store the partial match length.
812+
partialMatchLengthAtStack.set(stackIndex, matchLength);
815813
}
814+
} else if (!funcMatchesImplementation(thread, funcIndex)) {
815+
// This stack's function does not match the CallNodePath, however it's not part
816+
// of the CallNodePath's implementation filter. Inherit the parent's partial
817+
// match length
818+
partialMatchLengthAtStack.set(stackIndex, prefixPartialMatchLength);
816819
} else {
817-
// This stack is not part of a matching branch of the tree.
818-
doesMatchCallNodePath = false;
819-
}
820-
stackMatches[stackIndex] = doesMatchCallNodePath;
821-
stackDepths[stackIndex] = stackDepth;
822-
823-
// Map the oldStackToNewStack, and only push on the stacks that aren't merged.
824-
if (doMerge) {
825-
const newStackPrefix = oldStackToNewStack.get(prefix);
826-
oldStackToNewStack.set(
827-
stackIndex,
828-
newStackPrefix === undefined ? null : newStackPrefix
829-
);
830-
} else {
831-
const newStackIndex = newLength++;
832-
const newStackPrefix = oldStackToNewStack.get(prefix);
833-
newPrefixCol[newStackIndex] =
834-
newStackPrefix === undefined ? null : newStackPrefix;
835-
newFrameCol[newStackIndex] = frameIndex;
836-
newCategoryCol[newStackIndex] = category;
837-
newSubcategoryCol[newStackIndex] = subcategory;
838-
oldStackToNewStack.set(stackIndex, newStackIndex);
820+
// While all of the predecessors matched, this stack's function does not :(
839821
}
840822
}
841823

842824
const newStackTable = {
843-
frame: newFrameCol,
825+
...stackTable,
844826
prefix: newPrefixCol,
845-
category: new Uint8Array(newCategoryCol),
846-
subcategory:
847-
stackTable.subcategory instanceof Uint8Array
848-
? new Uint8Array(newSubcategoryCol)
849-
: new Uint16Array(newSubcategoryCol),
850-
length: newLength,
851827
};
852828

853-
return updateThreadStacks(
854-
thread,
855-
newStackTable,
856-
getMapStackUpdater(oldStackToNewStack)
857-
);
829+
return updateThreadStacks(thread, newStackTable, (oldStack) => {
830+
const effectiveParent = mergedStackToEffectiveParent.get(oldStack);
831+
return effectiveParent !== undefined ? effectiveParent : oldStack;
832+
});
858833
});
859834
}
860835

0 commit comments

Comments
 (0)