Skip to content

Commit ac19bf3

Browse files
authored
Modernize more of the transform functions (#5934)
Two changes: - Use createStackTableBySkippingDiscarded in focusSubtree, focusFunction, and focusCategory. - Change mergeCallNode to only create a new prefix column, like mergeFunction.
2 parents 64becd0 + 46e3594 commit ac19bf3

1 file changed

Lines changed: 105 additions & 200 deletions

File tree

src/profile-logic/transforms.ts

Lines changed: 105 additions & 200 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

@@ -1257,29 +1232,18 @@ export function focusSubtree(
12571232
const prefixDepth = callNodePath.length;
12581233
const stackMatches = new Int32Array(stackTable.length);
12591234
const funcMatchesImplementation = FUNC_MATCHES[implementation];
1260-
const oldStackToNewStack: Map<
1261-
IndexIntoStackTable | null,
1262-
IndexIntoStackTable | null
1263-
> = new Map();
1264-
// A root stack's prefix will be null. Maintain that relationship from old to new
1265-
// stacks by mapping from null to null.
1266-
oldStackToNewStack.set(null, null);
1267-
const newFrameCol = [];
1268-
const newPrefixCol = [];
1269-
const newCategoryCol = [];
1270-
const newSubcategoryCol = [];
1271-
let newLength = 0;
1235+
const oldStackToNewStack = new Int32Array(stackTable.length).fill(-1);
1236+
const newPrefixCol: Array<IndexIntoStackTable | null> = [];
1237+
const keepStack = makeBitSet(stackTable.length);
12721238
for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) {
12731239
const prefix = stackTable.prefix[stackIndex];
12741240
const prefixMatchesUpTo = prefix !== null ? stackMatches[prefix] : 0;
12751241
let stackMatchesUpTo = -1;
12761242
if (prefixMatchesUpTo !== -1) {
1277-
const frame = stackTable.frame[stackIndex];
1278-
const category = stackTable.category[stackIndex];
1279-
const subcategory = stackTable.subcategory[stackIndex];
12801243
if (prefixMatchesUpTo === prefixDepth) {
12811244
stackMatchesUpTo = prefixDepth;
12821245
} else {
1246+
const frame = stackTable.frame[stackIndex];
12831247
const funcIndex = frameTable.func[frame];
12841248
if (funcIndex === callNodePath[prefixMatchesUpTo]) {
12851249
stackMatchesUpTo = prefixMatchesUpTo + 1;
@@ -1288,41 +1252,25 @@ export function focusSubtree(
12881252
}
12891253
}
12901254
if (stackMatchesUpTo === prefixDepth) {
1291-
const newStackIndex = newLength++;
1292-
const newStackPrefix = oldStackToNewStack.get(prefix);
1293-
newPrefixCol[newStackIndex] = newStackPrefix ?? null;
1294-
newFrameCol[newStackIndex] = frame;
1295-
newCategoryCol[newStackIndex] = category;
1296-
newSubcategoryCol[newStackIndex] = subcategory;
1297-
oldStackToNewStack.set(stackIndex, newStackIndex);
1255+
const prefixNewStack =
1256+
prefix === null ? -1 : oldStackToNewStack[prefix];
1257+
oldStackToNewStack[stackIndex] = newPrefixCol.length;
1258+
newPrefixCol.push(prefixNewStack === -1 ? null : prefixNewStack);
1259+
setBit(keepStack, stackIndex);
12981260
}
12991261
}
13001262
stackMatches[stackIndex] = stackMatchesUpTo;
13011263
}
13021264

1303-
const newStackTable = {
1304-
frame: newFrameCol,
1305-
prefix: newPrefixCol,
1306-
category: new Uint8Array(newCategoryCol),
1307-
subcategory:
1308-
stackTable.subcategory instanceof Uint8Array
1309-
? new Uint8Array(newSubcategoryCol)
1310-
: new Uint16Array(newSubcategoryCol),
1311-
length: newLength,
1312-
};
1313-
1314-
return updateThreadStacks(thread, newStackTable, (oldStack) => {
1315-
if (oldStack === null || stackMatches[oldStack] !== prefixDepth) {
1316-
return null;
1317-
}
1318-
const newStack = oldStackToNewStack.get(oldStack);
1319-
if (newStack === undefined) {
1320-
throw new Error(
1321-
'Converting from the old stack to a new stack cannot be undefined'
1322-
);
1323-
}
1324-
return newStack;
1325-
});
1265+
const newStackTable = createStackTableBySkippingDiscarded(
1266+
stackTable,
1267+
newPrefixCol,
1268+
keepStack
1269+
);
1270+
return applyTransformOutputToThread(
1271+
{ newStackTable, effectOnThreadData: { oldStackToNewStack } },
1272+
thread
1273+
);
13261274
});
13271275
}
13281276

@@ -1386,53 +1334,32 @@ export function focusFunction(
13861334
): Thread {
13871335
return timeCode('focusFunction', () => {
13881336
const { stackTable, frameTable } = thread;
1389-
// A map oldStack -> newStack+1, implemented as a Uint32Array for performance.
1390-
// If newStack+1 is zero it means "null", i.e. this stack was filtered out.
1391-
// Typed arrays are initialized to zero, which we interpret as null.
1392-
const oldStackToNewStackPlusOne = new Uint32Array(stackTable.length);
1393-
1394-
const newFrameCol = [];
1395-
const newPrefixCol = [];
1396-
const newCategoryCol = [];
1397-
const newSubcategoryCol = [];
1398-
let newLength = 0;
1337+
const oldStackToNewStack = new Int32Array(stackTable.length).fill(-1);
1338+
const newPrefixCol: Array<IndexIntoStackTable | null> = [];
1339+
const keepStack = makeBitSet(stackTable.length);
13991340

14001341
for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) {
14011342
const prefix = stackTable.prefix[stackIndex];
14021343
const frameIndex = stackTable.frame[stackIndex];
14031344
const funcIndex = frameTable.func[frameIndex];
14041345

1405-
const newPrefixPlusOne =
1406-
prefix === null ? 0 : oldStackToNewStackPlusOne[prefix];
1407-
const newPrefix = newPrefixPlusOne === 0 ? null : newPrefixPlusOne - 1;
1408-
if (newPrefix !== null || funcIndex === funcIndexToFocus) {
1409-
const newStackIndex = newLength++;
1410-
newPrefixCol[newStackIndex] = newPrefix;
1411-
newFrameCol[newStackIndex] = frameIndex;
1412-
newCategoryCol[newStackIndex] = stackTable.category[stackIndex];
1413-
newSubcategoryCol[newStackIndex] = stackTable.subcategory[stackIndex];
1414-
oldStackToNewStackPlusOne[stackIndex] = newStackIndex + 1;
1346+
const prefixNewStack = prefix === null ? -1 : oldStackToNewStack[prefix];
1347+
if (prefixNewStack !== -1 || funcIndex === funcIndexToFocus) {
1348+
oldStackToNewStack[stackIndex] = newPrefixCol.length;
1349+
newPrefixCol.push(prefixNewStack === -1 ? null : prefixNewStack);
1350+
setBit(keepStack, stackIndex);
14151351
}
14161352
}
14171353

1418-
const newStackTable = {
1419-
frame: newFrameCol,
1420-
prefix: newPrefixCol,
1421-
category: new Uint8Array(newCategoryCol),
1422-
subcategory:
1423-
stackTable.subcategory instanceof Uint8Array
1424-
? new Uint8Array(newSubcategoryCol)
1425-
: new Uint16Array(newSubcategoryCol),
1426-
length: newLength,
1427-
};
1428-
1429-
return updateThreadStacks(thread, newStackTable, (oldStack) => {
1430-
if (oldStack === null) {
1431-
return null;
1432-
}
1433-
const newStackPlusOne = oldStackToNewStackPlusOne[oldStack];
1434-
return newStackPlusOne === 0 ? null : newStackPlusOne - 1;
1435-
});
1354+
const newStackTable = createStackTableBySkippingDiscarded(
1355+
stackTable,
1356+
newPrefixCol,
1357+
keepStack
1358+
);
1359+
return applyTransformOutputToThread(
1360+
{ newStackTable, effectOnThreadData: { oldStackToNewStack } },
1361+
thread
1362+
);
14361363
});
14371364
}
14381365

@@ -1498,56 +1425,34 @@ export function focusSelf(
14981425
export function focusCategory(thread: Thread, category: IndexIntoCategoryList) {
14991426
return timeCode('focusCategory', () => {
15001427
const { stackTable } = thread;
1501-
const oldStackToNewStack: Map<
1502-
IndexIntoStackTable | null,
1503-
IndexIntoStackTable | null
1504-
> = new Map();
1505-
oldStackToNewStack.set(null, null);
1506-
1507-
const newFrameCol = [];
1508-
const newPrefixCol = [];
1509-
const newCategoryCol = [];
1510-
const newSubcategoryCol = [];
1511-
let newLength = 0;
1428+
const oldStackToNewStack = new Int32Array(stackTable.length).fill(-1);
1429+
const newPrefixCol: Array<IndexIntoStackTable | null> = [];
1430+
const keepStack = makeBitSet(stackTable.length);
15121431

15131432
// fill the new stack table with the kept frames
15141433
for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) {
15151434
const prefix = stackTable.prefix[stackIndex];
1516-
const newPrefix = oldStackToNewStack.get(prefix);
1517-
if (newPrefix === undefined) {
1518-
throw new Error('The prefix should not map to an undefined value');
1519-
}
1435+
const prefixNewStack = prefix === null ? -1 : oldStackToNewStack[prefix];
15201436

15211437
if (stackTable.category[stackIndex] !== category) {
1522-
oldStackToNewStack.set(stackIndex, newPrefix);
1438+
oldStackToNewStack[stackIndex] = prefixNewStack;
15231439
continue;
15241440
}
15251441

1526-
const newStackIndex = newLength++;
1527-
newPrefixCol[newStackIndex] = newPrefix;
1528-
newFrameCol[newStackIndex] = stackTable.frame[stackIndex];
1529-
newCategoryCol[newStackIndex] = stackTable.category[stackIndex];
1530-
newSubcategoryCol[newStackIndex] = stackTable.subcategory[stackIndex];
1531-
oldStackToNewStack.set(stackIndex, newStackIndex);
1442+
oldStackToNewStack[stackIndex] = newPrefixCol.length;
1443+
newPrefixCol.push(prefixNewStack === -1 ? null : prefixNewStack);
1444+
setBit(keepStack, stackIndex);
15321445
}
15331446

1534-
const newStackTable = {
1535-
frame: newFrameCol,
1536-
prefix: newPrefixCol,
1537-
category: new Uint8Array(newCategoryCol),
1538-
subcategory:
1539-
stackTable.subcategory instanceof Uint8Array
1540-
? new Uint8Array(newSubcategoryCol)
1541-
: new Uint16Array(newSubcategoryCol),
1542-
length: newLength,
1543-
};
1544-
1545-
const updated = updateThreadStacks(
1546-
thread,
1547-
newStackTable,
1548-
getMapStackUpdater(oldStackToNewStack)
1447+
const newStackTable = createStackTableBySkippingDiscarded(
1448+
stackTable,
1449+
newPrefixCol,
1450+
keepStack
1451+
);
1452+
return applyTransformOutputToThread(
1453+
{ newStackTable, effectOnThreadData: { oldStackToNewStack } },
1454+
thread
15491455
);
1550-
return updated;
15511456
});
15521457
}
15531458

0 commit comments

Comments
 (0)