Perf: strip more branches

This commit is contained in:
SukkaW 2025-03-02 00:05:53 +08:00
parent e6e6c23495
commit 64317794b0

View File

@ -13,10 +13,11 @@ const START = 1 << 1;
const INCLUDE_ALL_SUBDOMAIN = 1 << 2; const INCLUDE_ALL_SUBDOMAIN = 1 << 2;
type TrieNode<Meta = any> = [ type TrieNode<Meta = any> = [
flag: number, /** end, includeAllSubdomain (.example.org, ||example.com) */ /** end, includeAllSubdomain (.example.org, ||example.com) */ flag: number,
TrieNode | null, /** parent */ /** parent */ TrieNode | null,
Map<string, TrieNode>, /** children */ /** children */ Map<string, TrieNode>,
Meta /** meta */ /** token */ token: string,
/** meta */ Meta
]; ];
function deepTrieNodeToJSON<Meta = unknown>(node: TrieNode, function deepTrieNodeToJSON<Meta = unknown>(node: TrieNode,
@ -25,11 +26,11 @@ function deepTrieNodeToJSON<Meta = unknown>(node: TrieNode,
obj['[start]'] = getBit(node[0], START); obj['[start]'] = getBit(node[0], START);
obj['[subdomain]'] = getBit(node[0], INCLUDE_ALL_SUBDOMAIN); obj['[subdomain]'] = getBit(node[0], INCLUDE_ALL_SUBDOMAIN);
if (node[3] != null) { if (node[4] != null) {
if (unpackMeta) { if (unpackMeta) {
obj['[meta]'] = unpackMeta(node[3]); obj['[meta]'] = unpackMeta(node[4]);
} else { } else {
obj['[meta]'] = node[3]; obj['[meta]'] = node[4];
} }
} }
node[2].forEach((value, key) => { node[2].forEach((value, key) => {
@ -38,7 +39,7 @@ function deepTrieNodeToJSON<Meta = unknown>(node: TrieNode,
return obj; return obj;
} }
const createNode = <Meta = unknown>(parent: TrieNode | null = null): TrieNode => [1, parent, new Map<string, TrieNode>(), null] as TrieNode<Meta>; const createNode = <Meta = unknown>(token: string, parent: TrieNode | null = null): TrieNode => [1, parent, new Map<string, TrieNode>(), token, null] as TrieNode<Meta>;
function hostnameToTokens(hostname: string, hostnameFromIndex: number): string[] { function hostnameToTokens(hostname: string, hostnameFromIndex: number): string[] {
const tokens = hostname.split('.'); const tokens = hostname.split('.');
@ -90,7 +91,7 @@ interface FindSingleChildLeafResult<Meta> {
} }
abstract class Triebase<Meta = unknown> { abstract class Triebase<Meta = unknown> {
protected readonly $root: TrieNode<Meta> = createNode(); protected readonly $root: TrieNode<Meta> = createNode('$root');
protected $size = 0; protected $size = 0;
get root() { get root() {
@ -259,7 +260,7 @@ abstract class Triebase<Meta = unknown> {
// If the node is a sentinel, we push the suffix to the results // If the node is a sentinel, we push the suffix to the results
if (getBit(node[0], START)) { if (getBit(node[0], START)) {
onMatches(suffix, getBit(node[0], INCLUDE_ALL_SUBDOMAIN), node[3]); onMatches(suffix, getBit(node[0], INCLUDE_ALL_SUBDOMAIN), node[4]);
} }
} while (nodeStack.length); } while (nodeStack.length);
}; };
@ -303,7 +304,7 @@ abstract class Triebase<Meta = unknown> {
// If the node is a sentinel, we push the suffix to the results // If the node is a sentinel, we push the suffix to the results
if (getBit(node[0], START)) { if (getBit(node[0], START)) {
onMatches(suffix, getBit(node[0], INCLUDE_ALL_SUBDOMAIN), node[3]); onMatches(suffix, getBit(node[0], INCLUDE_ALL_SUBDOMAIN), node[4]);
} }
} while (nodeStack.length); } while (nodeStack.length);
}; };
@ -317,19 +318,16 @@ abstract class Triebase<Meta = unknown> {
const child = node[2]; const child = node[2];
// console.log({ const childSize = child.size + (getBit(node[0], INCLUDE_ALL_SUBDOMAIN) ? 1 : 0);
// child, parent, token
// });
// console.log(this.inspect(0));
if (toPrune !== null) { // the most near branch that could potentially being pruned if (toPrune !== null) { // the most near branch that could potentially being pruned
if (child.size > 1) { if (childSize >= 1) {
// The branch has some children, the branch need retain. // The branch has some children, the branch need retain.
// And we need to abort prune that parent branch, so we set it to null // And we need to abort prune that parent branch, so we set it to null
toPrune = null; toPrune = null;
tokenToPrune = null; tokenToPrune = null;
} }
} else if (child.size < 1) { } else if (childSize < 1) {
// There is only one token child, or no child at all, we can prune it safely // There is only one token child, or no child at all, we can prune it safely
// It is now the top-est branch that could potentially being pruned // It is now the top-est branch that could potentially being pruned
toPrune = parent; toPrune = parent;
@ -534,7 +532,7 @@ export class HostnameSmolTrie<Meta = unknown> extends Triebase<Meta> {
return true; return true;
} }
} else { } else {
const newNode = createNode(node); const newNode = createNode(token, node);
curNodeChildren.set(token, newNode); curNodeChildren.set(token, newNode);
node = newNode; node = newNode;
} }
@ -571,7 +569,7 @@ export class HostnameSmolTrie<Meta = unknown> extends Triebase<Meta> {
} else { } else {
node[0] = deleteBit(node[0], INCLUDE_ALL_SUBDOMAIN); node[0] = deleteBit(node[0], INCLUDE_ALL_SUBDOMAIN);
} }
node[3] = meta!; node[4] = meta!;
} }
public whitelist(suffix: string, includeAllSubdomain = suffix[0] === '.', hostnameFromIndex = suffix[0] === '.' ? 1 : 0) { public whitelist(suffix: string, includeAllSubdomain = suffix[0] === '.', hostnameFromIndex = suffix[0] === '.' ? 1 : 0) {
@ -585,25 +583,43 @@ export class HostnameSmolTrie<Meta = unknown> extends Triebase<Meta> {
if (includeAllSubdomain) { if (includeAllSubdomain) {
// If there is a `[start]sub.example.com` here, remove it // If there is a `[start]sub.example.com` here, remove it
node[0] = deleteBit(node[0], INCLUDE_ALL_SUBDOMAIN); node[0] = deleteBit(node[0], INCLUDE_ALL_SUBDOMAIN);
node[0] = deleteBit(node[0], START);
// Removing all the child nodes by empty the children // Removing all the child nodes by empty the children
node[2].clear(); node[2].clear();
// we do not remove sub.example.com for now, we will do that later
} else { } else {
// Trying to whitelist `example.com` when there is already a `.example.com` in the trie // Trying to whitelist `example.com` when there is already a `.example.com` in the trie
node[0] = deleteBit(node[0], INCLUDE_ALL_SUBDOMAIN); node[0] = deleteBit(node[0], INCLUDE_ALL_SUBDOMAIN);
} }
// return early if not found if (includeAllSubdomain) {
if (missingBit(node[0], START)) return; node[1]?.[2].delete(node[3]);
} else if (missingBit(node[0], START) && node[1]) {
return;
}
if (toPrune && tokenToPrune) { if (toPrune && tokenToPrune) {
toPrune[2].delete(tokenToPrune); toPrune[2].delete(tokenToPrune);
} else { } else {
node[0] = deleteBit(node[0], START); node[0] = deleteBit(node[0], START);
} }
cleanUpEmptyNode(node);
}; };
} }
function cleanUpEmptyNode(node: TrieNode<unknown>) {
if (
missingBit(node[0], START)
&& node[2].size === 0
&& missingBit(node[0], INCLUDE_ALL_SUBDOMAIN)
&& node[1]
) {
node[1][2].delete(node[3]);
cleanUpEmptyNode(node[1]);
}
}
export class HostnameTrie<Meta = unknown> extends Triebase<Meta> { export class HostnameTrie<Meta = unknown> extends Triebase<Meta> {
get size() { get size() {
return this.$size; return this.$size;
@ -618,7 +634,7 @@ export class HostnameTrie<Meta = unknown> extends Triebase<Meta> {
if (child.has(token)) { if (child.has(token)) {
node = child.get(token)!; node = child.get(token)!;
} else { } else {
const newNode = createNode(node); const newNode = createNode(token, node);
child.set(token, newNode); child.set(token, newNode);
node = newNode; node = newNode;
} }
@ -644,7 +660,7 @@ export class HostnameTrie<Meta = unknown> extends Triebase<Meta> {
} else { } else {
node[0] = deleteBit(node[0], INCLUDE_ALL_SUBDOMAIN); node[0] = deleteBit(node[0], INCLUDE_ALL_SUBDOMAIN);
} }
node[3] = meta!; node[4] = meta!;
} }
} }