From fe06774a19b2156d92f11481b35ee632c98bf470 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Thu, 23 Jan 2025 22:30:04 +0800 Subject: [PATCH] Perf: minor improvements here and there --- Build/lib/fetch-retry.ts | 2 +- Build/lib/parse-filter/filters.ts | 12 ++++-- Build/lib/trie.test.ts | 41 ++++++++++++------- Build/lib/trie.ts | 67 +++++++++++++++++++------------ Build/trace/index.ts | 2 +- 5 files changed, 78 insertions(+), 46 deletions(-) diff --git a/Build/lib/fetch-retry.ts b/Build/lib/fetch-retry.ts index c70ea10e..3def686d 100644 --- a/Build/lib/fetch-retry.ts +++ b/Build/lib/fetch-retry.ts @@ -23,7 +23,7 @@ if (!fs.existsSync(CACHE_DIR)) { fs.mkdirSync(CACHE_DIR, { recursive: true }); } -const agent = new Agent({ allowH2: true }); +const agent = new Agent({ allowH2: false }); setGlobalDispatcher(agent.compose( interceptors.retry({ diff --git a/Build/lib/parse-filter/filters.ts b/Build/lib/parse-filter/filters.ts index 2e276154..892cd217 100644 --- a/Build/lib/parse-filter/filters.ts +++ b/Build/lib/parse-filter/filters.ts @@ -111,10 +111,8 @@ export function processFilterRulesWithPreload( }); } -// const R_KNOWN_NOT_NETWORK_FILTER_PATTERN_2 = /(\$popup|\$removeparam|\$popunder|\$cname)/; -// cname exceptional filter can not be parsed by NetworkFilter -// Surge / Clash can't handle CNAME either, so we just ignore them - +// many filter that has modifiers can not work on Surge/Clash because browser context is required +// we can early bail out those rules const kwfilter = createKeywordFilter([ '!', '?', @@ -132,10 +130,16 @@ const kwfilter = createKeywordFilter([ // special modifier '$popup', '$removeparam', + '$redirect', '$popunder', '$cname', '$frame', '$domain', + '$from', + '$to', + '$csp', + '$replace', + '$urlskip', // some bad syntax '^popup' ]); diff --git a/Build/lib/trie.test.ts b/Build/lib/trie.test.ts index 25fede25..79381530 100644 --- a/Build/lib/trie.test.ts +++ b/Build/lib/trie.test.ts @@ -91,8 +91,7 @@ describe('Trie', () => { trie.add('example.com'); trie.add('moe.sb'); - expect(trie.delete('')).toBe(false); - expect(trie.delete('')).toBe(false); + expect(trie.delete('no-match.com')).toBe(false); expect(trie.delete('example.org')).toBe(false); expect(trie.delete('skk.moe')).toBe(true); @@ -131,7 +130,7 @@ describe('Trie', () => { expect(trie.find('.example.com')).toStrictEqual(['blog.example.com', 'cdn.example.com']); expect(trie.find('org')).toStrictEqual(['example.org']); expect(trie.find('example.net')).toStrictEqual([]); - expect(trie.find('')).toStrictEqual(['example.com', 'example.org', 'blog.example.com', 'cdn.example.com']); + expect(trie.dump()).toStrictEqual(['example.com', 'example.org', 'blog.example.com', 'cdn.example.com']); }); it('should be possible to retrieve items matching the given prefix even with a smol trie', () => { @@ -148,7 +147,7 @@ describe('Trie', () => { expect(trie.find('.example.com')).toStrictEqual(['.example.com']); expect(trie.find('org')).toStrictEqual(['example.org']); expect(trie.find('example.net')).toStrictEqual([]); - expect(trie.find('')).toStrictEqual(['.example.com', 'example.org']); + expect(trie.dump()).toStrictEqual(['.example.com', 'example.org']); }); it('should be possible to create a trie from an arbitrary iterable.', () => { @@ -192,6 +191,28 @@ describe('surge domainset dedupe', () => { }); describe('smol tree', () => { + it('should init tree', () => { + const trie = createTrie([ + 'skk.moe', + 'anotherskk.moe', + 'blog.anotherskk.moe', + 'blog.skk.moe', + '.cdn.local', + 'blog.img.skk.local', + 'img.skk.local' + ], true); + + expect(trie.dump()).toStrictEqual([ + 'skk.moe', + 'anotherskk.moe', + '.cdn.local', + 'blog.skk.moe', + 'blog.anotherskk.moe', + 'img.skk.local', + 'blog.img.skk.local' + ]); + }); + it('should create simple tree - 1', () => { const trie = createTrie([ '.skk.moe', 'blog.skk.moe', '.cdn.skk.moe', 'skk.moe', @@ -264,7 +285,7 @@ describe('smol tree', () => { ]); }); - it('should efficiently whitelist domains', () => { + it('should effctly whitelist domains', () => { const trie = createTrie([ 'skk.moe', 'anotherskk.moe', @@ -275,16 +296,6 @@ describe('smol tree', () => { 'img.skk.local' ], true); - expect(trie.dump()).toStrictEqual([ - 'skk.moe', - 'anotherskk.moe', - '.cdn.local', - 'blog.skk.moe', - 'blog.anotherskk.moe', - 'img.skk.local', - 'blog.img.skk.local' - ]); - trie.whitelist('.skk.moe'); expect(trie.dump()).toStrictEqual([ diff --git a/Build/lib/trie.ts b/Build/lib/trie.ts index 0d93d27b..1c141aea 100644 --- a/Build/lib/trie.ts +++ b/Build/lib/trie.ts @@ -41,7 +41,7 @@ function deepTrieNodeToJSON(node: TrieNode, const createNode = (parent: TrieNode | null = null): TrieNode => [1, parent, new Map(), null] as TrieNode; -export function hostnameToTokens(hostname: string, hostnameFromIndex: number): string[] { +function hostnameToTokens(hostname: string, hostnameFromIndex: number): string[] { const tokens = hostname.split('.'); const results: string[] = []; let token = ''; @@ -50,6 +50,8 @@ export function hostnameToTokens(hostname: string, hostnameFromIndex: number): s token = tokens[i]; if (token.length > 0) { results.push(token); + } else { + throw new TypeError(JSON.stringify({ hostname, hostnameFromIndex }, null, 2)); } } @@ -117,7 +119,9 @@ abstract class Triebase { let parent: TrieNode = node; let token: string; + let child: Map> = node[2]; + // reverse lookup from end to start for (let i = tokens.length - 1; i >= 0; i--) { token = tokens[i]; @@ -127,8 +131,10 @@ abstract class Triebase { parent = node; - if (node[2].has(token)) { - node = node[2].get(token)!; + child = node[2]; + // cache node index access is 20% faster than direct access when doing twice + if (child.has(token)) { + node = child.get(token)!; } else { return null; } @@ -147,6 +153,8 @@ abstract class Triebase { let node: TrieNode = this.$root; let parent: TrieNode = node; + let child: Map> = node[2]; + const onToken = (token: string) => { // if (token === '') { // return true; @@ -154,8 +162,10 @@ abstract class Triebase { parent = node; - if (node[2].has(token)) { - node = node[2].get(token)!; + child = node[2]; + + if (child.has(token)) { + node = child.get(token)!; } else { return null; } @@ -204,12 +214,14 @@ abstract class Triebase { const node = nodeStack.shift()!; const suffix = suffixStack.shift()!; - if (node[2].size) { - const keys = Array.from(node[2].keys()).sort(Triebase.compare); + const child = node[2]; + + if (child.size) { + const keys = Array.from(child.keys()).sort(Triebase.compare); for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i]; - const childNode = node[2].get(key)!; + const childNode = child.get(key)!; // Pushing the child node to the stack for next iteration of DFS nodeStack.push(childNode); @@ -271,17 +283,18 @@ abstract class Triebase { suffixStack.push(initialSuffix); let node: TrieNode = initialNode; + let child: Map> = node[2]; do { node = nodeStack.shift()!; const suffix = suffixStack.shift()!; - - if (node[2].size) { - const keys = Array.from(node[2].keys()).sort(Triebase.compare); + child = node[2]; + if (child.size) { + const keys = Array.from(child.keys()).sort(Triebase.compare); for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i]; - const childNode = node[2].get(key)!; + const childNode = child.get(key)!; // Pushing the child node to the stack for next iteration of DFS nodeStack.push(childNode); @@ -303,18 +316,21 @@ abstract class Triebase { const onLoop = (node: TrieNode, parent: TrieNode, token: string) => { // Keeping track of a potential branch to prune - // Even if the node size is 1, but the single child is ".", we should retain the branch - // Since the "." could be special if it is the leaf-est node - const onlyChild = node[2].size === 0 && !node[1]; + const child = node[2]; - if (toPrune != null) { // the top-est branch that could potentially being pruned - if (!onlyChild) { - // The branch has moew than single child, retain the branch. - // And we need to abort prune the parent, so we set it to null + // console.log({ + // child, parent, token + // }); + // console.log(this.inspect(0)); + + if (toPrune !== null) { // the most near branch that could potentially being pruned + if (child.size > 1) { + // The branch has some children, the branch need retain. + // And we need to abort prune that parent branch, so we set it to null toPrune = null; tokenToPrune = null; } - } else if (onlyChild) { + } else if (child.size < 1) { // 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 toPrune = parent; @@ -552,7 +568,6 @@ export class HostnameSmolTrie extends Triebase { public whitelist(suffix: string, includeAllSubdomain = suffix[0] === '.', hostnameFromIndex = suffix[0] === '.' ? 1 : 0) { const tokens = hostnameToTokens(suffix, hostnameFromIndex); const res = this.getSingleChildLeaf(tokens); - if (res === null) return; const { node, toPrune, tokenToPrune } = res; @@ -572,7 +587,7 @@ export class HostnameSmolTrie extends Triebase { // return early if not found if (missingBit(node[0], START)) return; - if (tokenToPrune && toPrune) { + if (toPrune && tokenToPrune) { toPrune[2].delete(tokenToPrune); } else { node[0] = deleteBit(node[0], START); @@ -587,13 +602,15 @@ export class HostnameTrie extends Triebase { add(suffix: string, includeAllSubdomain = suffix[0] === '.', meta?: Meta, hostnameFromIndex = suffix[0] === '.' ? 1 : 0): void { let node: TrieNode = this.$root; + let child: Map> = node[2]; const onToken = (token: string) => { - if (node[2].has(token)) { - node = node[2].get(token)!; + child = node[2]; + if (child.has(token)) { + node = child.get(token)!; } else { const newNode = createNode(node); - node[2].set(token, newNode); + child.set(token, newNode); node = newNode; } diff --git a/Build/trace/index.ts b/Build/trace/index.ts index 53100019..1462b508 100644 --- a/Build/trace/index.ts +++ b/Build/trace/index.ts @@ -120,7 +120,7 @@ export function task(importMetaMain: boolean, importMetaPath: string) { dummySpan.traceChildAsync('dummy', (childSpan) => fn(childSpan, onCleanup)).finally(() => { dummySpan.stop(); printTraceResult(dummySpan.traceResult); - whyIsNodeRunning(); + process.nextTick(whyIsNodeRunning); }); }