Skip to content

Commit c8f1a69

Browse files
committed
Change the raw stack table to store the prefix as an index offset.
Replace `RawStackTable.prefix` with a `prefixOffset` column. For each stack i, `prefixOffset[i] === 0` means i is a root, and `prefixOffset[i] === k` (with k > 0) means i's parent is at index i - k. All values are non-negative because the stack table is stored in topological order. The motivation is profile size: relative offsets are typically small numbers (stack depth) instead of large absolute indexes, so they compress better in the on-wire JSON. The derived `StackTable.prefix` keeps its absolute Int32Array representation for consumers that prefer that form.
1 parent 7404552 commit c8f1a69

20 files changed

Lines changed: 456480 additions & 456387 deletions

docs-developer/CHANGELOG-formats.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ Note that this is not an exhaustive list. Processed profile format upgraders can
66

77
## Processed profile format
88

9+
### Version 67
10+
11+
The `prefix` column of `profile.shared.stackTable` was replaced with a
12+
`prefixOffset` column, to improve compressibility.
13+
14+
The meanings are as follows:
15+
16+
- `prefixOffset[i] === 0` means: stack `i` is a root.
17+
- Otherwise, `prefixOffset[i] === k` (k > 0) means: `i`'s parent has index `i - k`.
18+
19+
Parents always come before their children in the stack table (the stack table is stored in topological order), so these offsets are always positive and always point backwards.
20+
21+
The `prefixOffset` column can be stored as an array of numbers or as an `Int32Array` (when using [JsonSlabs](https://github.com/mstange/json-slabs/)).
22+
923
### Version 66
1024

1125
The `prefix` column of `profile.shared.stackTable` now uses `-1` instead of `null` to indicate "this stack node is a root".

src/app-logic/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const GECKO_PROFILE_VERSION = 34;
1212
// The current version of the "processed" profile format.
1313
// Please don't forget to update the processed profile format changelog in
1414
// `docs-developer/CHANGELOG-formats.md`.
15-
export const PROCESSED_PROFILE_VERSION = 66;
15+
export const PROCESSED_PROFILE_VERSION = 67;
1616

1717
// The following are the margin sizes for the left and right of the timeline. Independent
1818
// components need to share these values.

src/app-logic/url-handling.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,7 +1654,8 @@ function getStackIndexFromVersion3JSCallNodePath(
16541654
stackIndexDepth.set(-1, -1);
16551655

16561656
for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) {
1657-
const prefix = stackTable.prefix[stackIndex];
1657+
const offset = stackTable.prefixOffset[stackIndex];
1658+
const prefix = offset === 0 ? -1 : stackIndex - offset;
16581659
const frameIndex = stackTable.frame[stackIndex];
16591660
const funcIndex = frameTable.func[frameIndex];
16601661
const isJS = funcTable.isJS[funcIndex];
@@ -1703,7 +1704,8 @@ function getVersion4JSCallNodePathFromStackIndex(
17031704
if (funcTable.isJS[funcIndex] || funcTable.relevantForJS[funcIndex]) {
17041705
callNodePath.unshift(funcIndex);
17051706
}
1706-
nextStackIndex = stackTable.prefix[nextStackIndex];
1707+
const offset = stackTable.prefixOffset[nextStackIndex];
1708+
nextStackIndex = offset === 0 ? -1 : nextStackIndex - offset;
17071709
}
17081710
return callNodePath;
17091711
}

src/profile-logic/data-structures.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ export function getRawStackTableBuilderWithExistingContents(
7272
): RawStackTableBuilder {
7373
const prefix = new Array<IndexIntoStackTable | null>(existing.length);
7474
for (let i = 0; i < existing.length; i++) {
75-
const p = existing.prefix[i];
76-
prefix[i] = p === -1 ? null : p;
75+
const offset = existing.prefixOffset[i];
76+
prefix[i] = offset === 0 ? null : i - offset;
7777
}
7878
return {
7979
frame: [...existing.frame],
@@ -86,14 +86,14 @@ export function finishRawStackTableBuilder(
8686
builder: RawStackTableBuilder
8787
): RawStackTable {
8888
const { frame, prefix, length } = builder;
89-
const prefixCol = new Int32Array(length);
89+
const prefixOffset = new Int32Array(length);
9090
for (let i = 0; i < length; i++) {
9191
const p = prefix[i];
92-
prefixCol[i] = p === null ? -1 : p;
92+
prefixOffset[i] = p === null ? 0 : i - p;
9393
}
9494
return {
9595
frame: new Int32Array(frame),
96-
prefix: prefixCol,
96+
prefixOffset,
9797
length,
9898
};
9999
}

src/profile-logic/insert-stack-labels.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,10 @@ export function insertStackLabels(
196196
);
197197
let stacksToInsertCount = 0;
198198
for (let stackIndex = 0; stackIndex < oldStackTable.length; stackIndex++) {
199-
const parentStackIndex = oldStackTable.prefix[stackIndex];
199+
const prefixOffset = oldStackTable.prefixOffset[stackIndex];
200200
const inheritedLabelFrameIndex =
201-
parentStackIndex !== -1
202-
? inheritedLabelFrameIndexAtStack[parentStackIndex]
201+
prefixOffset !== 0
202+
? inheritedLabelFrameIndexAtStack[stackIndex - prefixOffset]
203203
: null;
204204
const frameIndex = oldStackTable.frame[stackIndex];
205205
const funcIndex = oldFrameTable.func[frameIndex];
@@ -217,7 +217,7 @@ export function insertStackLabels(
217217
) {
218218
labelFrameIndexToInsertAtStack[stackIndex] = null;
219219
inheritedLabelFrameIndexAtStack[stackIndex] = null;
220-
} else if (parentStackIndex === -1) {
220+
} else if (prefixOffset === 0) {
221221
labelFrameIndexToInsertAtStack[stackIndex] = rootLabelFrameIndex;
222222
inheritedLabelFrameIndexAtStack[stackIndex] = rootLabelFrameIndex;
223223
stacksToInsertCount++;
@@ -229,7 +229,7 @@ export function insertStackLabels(
229229

230230
// Now compute the new stack table.
231231
const newStackCount = oldStackTable.length + stacksToInsertCount;
232-
const newPrefixCol = new Int32Array(newStackCount);
232+
const newPrefixOffsetCol = new Int32Array(newStackCount);
233233
const newFrameCol = new Array<IndexIntoFrameTable>(newStackCount);
234234
const oldStackToNewStackPlusOne = new Int32Array(oldStackTable.length);
235235
let nextNewStackIndex = 0;
@@ -240,18 +240,22 @@ export function insertStackLabels(
240240
) {
241241
const labelFrameIndexToInsert =
242242
labelFrameIndexToInsertAtStack[oldStackIndex];
243-
const oldPrefix = oldStackTable.prefix[oldStackIndex];
243+
const oldPrefixOffset = oldStackTable.prefixOffset[oldStackIndex];
244+
const oldPrefix =
245+
oldPrefixOffset !== 0 ? oldStackIndex - oldPrefixOffset : -1;
244246
let newPrefix =
245247
oldPrefix !== -1 ? oldStackToNewStackPlusOne[oldPrefix] - 1 : -1;
246248
const frameIndex = oldStackTable.frame[oldStackIndex];
247249
if (labelFrameIndexToInsert !== null) {
248250
const insertedStackIndex = nextNewStackIndex++;
249-
newPrefixCol[insertedStackIndex] = newPrefix;
251+
newPrefixOffsetCol[insertedStackIndex] =
252+
newPrefix === -1 ? 0 : insertedStackIndex - newPrefix;
250253
newFrameCol[insertedStackIndex] = labelFrameIndexToInsert;
251254
newPrefix = insertedStackIndex;
252255
}
253256
const newStackIndex = nextNewStackIndex++;
254-
newPrefixCol[newStackIndex] = newPrefix;
257+
newPrefixOffsetCol[newStackIndex] =
258+
newPrefix === -1 ? 0 : newStackIndex - newPrefix;
255259
newFrameCol[newStackIndex] = frameIndex;
256260
oldStackToNewStackPlusOne[oldStackIndex] = newStackIndex + 1;
257261
}
@@ -265,7 +269,7 @@ export function insertStackLabels(
265269
}
266270

267271
const stackTable: RawStackTable = {
268-
prefix: newPrefixCol,
272+
prefixOffset: newPrefixOffsetCol,
269273
frame: newFrameCol,
270274
length: newStackCount,
271275
};

src/profile-logic/merge-compare.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1135,7 +1135,8 @@ function mergeStackTables(
11351135
stackTable.frame[i],
11361136
oldFrameToNewFramePlusOne
11371137
);
1138-
const prefix = stackTable.prefix[i];
1138+
const offset = stackTable.prefixOffset[i];
1139+
const prefix = offset === 0 ? -1 : i - offset;
11391140
const newPrefix =
11401141
prefix === -1 ? null : oldStackToNewStackPlusOne[prefix] - 1;
11411142

src/profile-logic/processed-profile-versioning.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ export function attemptToUpgradeProcessedProfileThroughMutation(
113113
// arrays. Idempotent; safe to run on an already-normalized profile.
114114
function _normalizeAfterUpgrade(profile: any): void {
115115
const stackTable = profile.shared?.stackTable ?? null;
116-
if (stackTable && !(stackTable.prefix instanceof Int32Array)) {
117-
stackTable.prefix = new Int32Array(stackTable.prefix);
116+
if (stackTable && !(stackTable.prefixOffset instanceof Int32Array)) {
117+
stackTable.prefixOffset = new Int32Array(stackTable.prefixOffset);
118118
}
119119
}
120120

@@ -3285,6 +3285,23 @@ const _upgraders: {
32853285
stackTable.prefix = newPrefix;
32863286
}
32873287
},
3288+
[67]: (profile: any) => {
3289+
// The stackTable.prefix column was replaced with a stackTable.prefixOffset
3290+
// column. For each stack i, prefixOffset[i] is 0 if i is a root, otherwise
3291+
// it is i's offset from its parent (parent index = i - prefixOffset[i]).
3292+
const stackTable = profile.shared?.stackTable ?? null;
3293+
if (stackTable && stackTable.prefix !== undefined) {
3294+
const oldPrefix = stackTable.prefix;
3295+
const length = oldPrefix.length;
3296+
const prefixOffset = new Int32Array(length);
3297+
for (let i = 0; i < length; i++) {
3298+
const p = oldPrefix[i];
3299+
prefixOffset[i] = p === -1 || p === null ? 0 : i - p;
3300+
}
3301+
stackTable.prefixOffset = prefixOffset;
3302+
delete stackTable.prefix;
3303+
}
3304+
},
32883305
// If you add a new upgrader here, please document the change in
32893306
// `docs-developer/CHANGELOG-formats.md`.
32903307
};

src/profile-logic/profile-compacting.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type ColumnDescription<TCol> = null extends (
5252
?
5353
| { type: 'INDEX_REF_INT32'; referencedTable: TableCompactionState }
5454
| { type: 'SELF_INDEX_REF_OR_NEG_ONE_INT32' }
55+
| { type: 'SELF_RELATIVE_PARENT' }
5556
| { type: 'NO_REF' }
5657
:
5758
| { type: 'INDEX_REF'; referencedTable: TableCompactionState }
@@ -88,6 +89,7 @@ const ColDesc = {
8889
selfIndexRefOrNegOneInt32: () => ({
8990
type: 'SELF_INDEX_REF_OR_NEG_ONE_INT32' as const,
9091
}),
92+
selfPrefixOffset: () => ({ type: 'SELF_RELATIVE_PARENT' as const }),
9193
noRef: () => ({ type: 'NO_REF' as const }),
9294
};
9395

@@ -161,7 +163,7 @@ export function computeCompactedProfile(
161163

162164
const stackTableDesc: TableDescription<RawStackTable> = {
163165
frame: ColDesc.indexRefInt32(tcs.frameTable),
164-
prefix: ColDesc.selfIndexRefOrNegOneInt32(),
166+
prefixOffset: ColDesc.selfPrefixOffset(),
165167
};
166168
const frameTableDesc: TableDescription<FrameTable> = {
167169
address: ColDesc.noRef(),
@@ -334,6 +336,8 @@ function _markTableAndComputeTranslation<T>(
334336
markSelfColumnWithNullableFields((table as any)[key], markBuffer);
335337
} else if (desc.type === 'SELF_INDEX_REF_OR_NEG_ONE_INT32') {
336338
markSelfColumnWithNegOneFieldsInt32((table as any)[key], markBuffer);
339+
} else if (desc.type === 'SELF_RELATIVE_PARENT') {
340+
markSelfColumnPrefixOffset((table as any)[key], markBuffer);
337341
}
338342
}
339343

@@ -355,6 +359,7 @@ function _markTableAndComputeTranslation<T>(
355359
break;
356360
case 'SELF_INDEX_REF_OR_NULL':
357361
case 'SELF_INDEX_REF_OR_NEG_ONE_INT32':
362+
case 'SELF_RELATIVE_PARENT':
358363
break; // already handled in the first pass
359364
case 'INDEX_REF_OR_NEG_ONE':
360365
markColumnWithNegOneableFields(
@@ -431,6 +436,20 @@ function markSelfColumnWithNegOneFieldsInt32(col: Int32Array, markBuf: BitSet) {
431436
}
432437
}
433438

439+
function markSelfColumnPrefixOffset(
440+
col: Int32Array<ArrayBuffer>,
441+
markBuf: BitSet
442+
) {
443+
for (let i = col.length - 1; i >= 0; i--) {
444+
if (checkBit(markBuf, i)) {
445+
const offset = col[i];
446+
if (offset !== 0) {
447+
setBit(markBuf, i - offset);
448+
}
449+
}
450+
}
451+
}
452+
434453
function markColumnWithNegOneableFields(
435454
col: Array<number | -1>,
436455
shouldMark: BitSet,
@@ -567,6 +586,14 @@ function _compactTable<T extends { length: number }>(
567586
newLength
568587
);
569588
break;
589+
case 'SELF_RELATIVE_PARENT':
590+
result[key] = _compactColSelfPrefixOffset(
591+
oldCol,
592+
markBuffer,
593+
thisTableCompactionState.oldIndexToNewIndexPlusOne,
594+
newLength
595+
);
596+
break;
570597
case 'INDEX_REF_OR_NEG_ONE':
571598
result[key] = _compactColIndexOrNegOne(
572599
oldCol,
@@ -684,6 +711,29 @@ function _compactColIndexOrNegOneInt32(
684711
return newCol;
685712
}
686713

714+
function _compactColSelfPrefixOffset(
715+
oldCol: Int32Array<ArrayBuffer>,
716+
markBuffer: BitSet,
717+
oldIndexToNewIndexPlusOne: Int32Array,
718+
newLength: number
719+
): Int32Array<ArrayBuffer> {
720+
const newCol = new Int32Array(newLength);
721+
let newIndex = 0;
722+
for (let i = 0; i < oldCol.length; i++) {
723+
if (checkBit(markBuffer, i)) {
724+
const offset = oldCol[i];
725+
if (offset === 0) {
726+
newCol[newIndex] = 0;
727+
} else {
728+
const newParent = oldIndexToNewIndexPlusOne[i - offset] - 1;
729+
newCol[newIndex] = newIndex - newParent;
730+
}
731+
newIndex++;
732+
}
733+
}
734+
return newCol;
735+
}
736+
687737
function _createCompactedThread(
688738
thread: RawThread,
689739
tcs: TableCompactionStates,

0 commit comments

Comments
 (0)