Skip to content

Commit 48f5a60

Browse files
committed
Speed up computeDeduplicatedCallNodeInfo with a bloom filter.
1 parent 924c69b commit 48f5a60

File tree

3 files changed

+101
-33
lines changed

3 files changed

+101
-33
lines changed

src/profile-logic/profile-data.js

+99-31
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ import type {
5454
IndexIntoStackTable,
5555
IndexIntoResourceTable,
5656
IndexIntoNativeSymbolTable,
57-
IndexIntoDeduplicatedCallNodeTable,
5857
DeduplicatedCallNodeInfo,
5958
ThreadIndex,
6059
Category,
@@ -1219,58 +1218,127 @@ export function getTimingsForCallNodeIndex(
12191218
return { forPath: pathTimings, rootTime };
12201219
}
12211220

1221+
function _hash32(val: number): number {
1222+
const rotated1 = val << 5;
1223+
const rotated2 = val >> 27;
1224+
const rotated = rotated1 | rotated2;
1225+
const xored = rotated ^ val;
1226+
return (xored * 0x9e_37_79_b9) | 0;
1227+
}
1228+
12221229
export function computeDeduplicatedCallNodeInfo(
12231230
callNodeInfo: CallNodeInfo
12241231
): DeduplicatedCallNodeInfo {
12251232
const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable();
12261233

1227-
let len = 0;
1228-
const prefixCol: IndexIntoDeduplicatedCallNodeTable[] = [];
1229-
const funcCol: IndexIntoFuncTable[] = [];
1230-
12311234
const callNodeCount = callNodeTable.length;
1235+
const funcCol = callNodeTable.func;
1236+
const prefixCol = callNodeTable.prefix;
1237+
const newPrefixCol = new Int32Array(callNodeCount);
1238+
const bloomBitsCol1 = new Int32Array(callNodeCount);
1239+
const bloomBitsCol2 = new Int32Array(callNodeCount);
1240+
const bloomBitsCol3 = new Int32Array(callNodeCount);
1241+
const bloomBitsCol4 = new Int32Array(callNodeCount);
1242+
12321243
const callNodeIndexToDeduplicatedCallNodeIndex = new Uint32Array(
12331244
callNodeCount
12341245
);
12351246

1247+
const funcBloomBits = new Int32Array(4);
12361248
for (let callNodeIndex = 0; callNodeIndex < callNodeCount; callNodeIndex++) {
1237-
const originalPrefix = callNodeTable.prefix[callNodeIndex];
1238-
const func = callNodeTable.func[callNodeIndex];
1239-
let prefix = -1;
1240-
if (originalPrefix !== -1) {
1241-
prefix = callNodeIndexToDeduplicatedCallNodeIndex[originalPrefix];
1242-
1243-
// Check if `func` is present in the prefix's call path.
1244-
let prefixHasFunc = false;
1245-
for (
1246-
let dedupAnc = prefix;
1247-
dedupAnc !== -1;
1248-
dedupAnc = prefixCol[dedupAnc]
1249-
) {
1250-
if (funcCol[dedupAnc] === func) {
1251-
prefixHasFunc = true;
1252-
break;
1249+
const prefix = prefixCol[callNodeIndex];
1250+
const func = funcCol[callNodeIndex];
1251+
1252+
const funcHash = _hash32(func);
1253+
const funcHashBitIndex1 = funcHash % 32;
1254+
const funcHashBitIndex2 = (funcHash >> 5) % 32;
1255+
// const funcHashBitIndex3 = (funcHash >> 10) % 32;
1256+
const funcHashBit1 = 1 << funcHashBitIndex1;
1257+
const funcHashBit2 = 1 << funcHashBitIndex2;
1258+
// const funcHashBit3 = 1 << funcHashBitIndex3;
1259+
const funcBitDistributor1 = (funcHash >> 15) & 0b11;
1260+
const funcBitDistributor2 = (funcHash >> 17) & 0b11;
1261+
// const funcBitDistributor3 = (funcHash >> 19) & 0b11;
1262+
1263+
funcBloomBits[0] = 0;
1264+
funcBloomBits[1] = 0;
1265+
funcBloomBits[2] = 0;
1266+
funcBloomBits[3] = 0;
1267+
funcBloomBits[funcBitDistributor1] |= funcHashBit1;
1268+
funcBloomBits[funcBitDistributor2] |= funcHashBit2;
1269+
// funcBloomBits[funcBitDistributor3] |= funcHashBit3;
1270+
1271+
const funcBloomBits1 = funcBloomBits[0];
1272+
const funcBloomBits2 = funcBloomBits[1];
1273+
const funcBloomBits3 = funcBloomBits[2];
1274+
const funcBloomBits4 = funcBloomBits[3];
1275+
1276+
let prefixBloomBits1 = 0;
1277+
let prefixBloomBits2 = 0;
1278+
let prefixBloomBits3 = 0;
1279+
let prefixBloomBits4 = 0;
1280+
let newPrefix = -1;
1281+
if (prefix !== -1) {
1282+
newPrefix = callNodeIndexToDeduplicatedCallNodeIndex[prefix];
1283+
prefixBloomBits1 = bloomBitsCol1[newPrefix];
1284+
prefixBloomBits2 = bloomBitsCol2[newPrefix];
1285+
prefixBloomBits3 = bloomBitsCol3[newPrefix];
1286+
prefixBloomBits4 = bloomBitsCol4[newPrefix];
1287+
1288+
// Check if `func` is present in the newPrefix's call path.
1289+
let funcIsAlreadyPresentInPrefix = false;
1290+
1291+
const hashMatched =
1292+
(prefixBloomBits1 & funcBloomBits1) === funcBloomBits1 &&
1293+
(prefixBloomBits2 & funcBloomBits2) === funcBloomBits2 &&
1294+
(prefixBloomBits3 & funcBloomBits3) === funcBloomBits3 &&
1295+
(prefixBloomBits4 & funcBloomBits4) === funcBloomBits4;
1296+
if (hashMatched) {
1297+
let dedupIndex = newPrefix;
1298+
for (let j = 0; ; j++) {
1299+
if (funcCol[dedupIndex] === func) {
1300+
funcIsAlreadyPresentInPrefix = true;
1301+
break;
1302+
}
1303+
dedupIndex = newPrefixCol[dedupIndex];
1304+
if (
1305+
dedupIndex === -1 ||
1306+
(j % 2 === 0 &&
1307+
((bloomBitsCol1[dedupIndex] & funcBloomBits1) !==
1308+
funcBloomBits1 ||
1309+
(bloomBitsCol2[dedupIndex] & funcBloomBits2) !==
1310+
funcBloomBits2 ||
1311+
(bloomBitsCol3[dedupIndex] & funcBloomBits3) !==
1312+
funcBloomBits3 ||
1313+
(bloomBitsCol4[dedupIndex] & funcBloomBits4) !==
1314+
funcBloomBits4))
1315+
) {
1316+
break;
1317+
}
12531318
}
12541319
}
1255-
1256-
if (prefixHasFunc) {
1320+
if (funcIsAlreadyPresentInPrefix) {
12571321
// Drop this node.
1258-
callNodeIndexToDeduplicatedCallNodeIndex[callNodeIndex] = prefix;
1322+
callNodeIndexToDeduplicatedCallNodeIndex[callNodeIndex] = newPrefix;
12591323
continue;
12601324
}
12611325
}
12621326

12631327
// Create a node in the deduplicated call node table.
1264-
const newIndex = len++;
1265-
prefixCol[newIndex] = prefix;
1266-
funcCol[newIndex] = func;
1267-
callNodeIndexToDeduplicatedCallNodeIndex[callNodeIndex] = newIndex;
1328+
newPrefixCol[callNodeIndex] = newPrefix;
1329+
bloomBitsCol1[callNodeIndex] = prefixBloomBits1 | funcBloomBits1;
1330+
bloomBitsCol2[callNodeIndex] = prefixBloomBits2 | funcBloomBits2;
1331+
bloomBitsCol3[callNodeIndex] = prefixBloomBits3 | funcBloomBits3;
1332+
bloomBitsCol4[callNodeIndex] = prefixBloomBits4 | funcBloomBits4;
1333+
callNodeIndexToDeduplicatedCallNodeIndex[callNodeIndex] = callNodeIndex;
12681334
}
12691335

1336+
// console.log({bloomBitsCol1: bloomBitsCol1.slice(0, 1000).map((num) => num.toString(2)) });
1337+
12701338
const deduplicatedCallNodeTable = {
1271-
length: len,
1272-
prefix: new Int32Array(prefixCol),
1273-
func: new Uint32Array(funcCol),
1339+
length: callNodeCount,
1340+
prefix: newPrefixCol,
1341+
func: funcCol,
12741342
};
12751343

12761344
return {

src/selectors/per-thread/stack-sample.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ import type {
4545
ThreadsKey,
4646
SelfAndTotal,
4747
CallNodeSelfAndSummary,
48+
DeduplicatedCallNodeInfo,
4849
} from 'firefox-profiler/types';
4950
import type {
5051
CallNodeInfo,
5152
CallNodeInfoInverted,
52-
DeduplicatedCallNodeInfo,
5353
} from 'firefox-profiler/profile-logic/call-node-info';
5454

5555
import type { ThreadSelectorsPerThread } from './thread';

src/types/profile-derived.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export type DeduplicatedCallNodeInfo = {|
130130
export type DeduplicatedCallNodeTable = {|
131131
length: number,
132132
prefix: Int32Array,
133-
func: Uint32Array,
133+
func: Int32Array,
134134
|};
135135

136136
export type LineNumber = number;

0 commit comments

Comments
 (0)