Browse Source

Perf: minor improvements here and there

SukkaW 1 year ago
parent
commit
fe06774a19
5 changed files with 78 additions and 46 deletions
  1. 1 1
      Build/lib/fetch-retry.ts
  2. 8 4
      Build/lib/parse-filter/filters.ts
  3. 26 15
      Build/lib/trie.test.ts
  4. 42 25
      Build/lib/trie.ts
  5. 1 1
      Build/trace/index.ts

+ 1 - 1
Build/lib/fetch-retry.ts

@@ -23,7 +23,7 @@ if (!fs.existsSync(CACHE_DIR)) {
   fs.mkdirSync(CACHE_DIR, { recursive: true });
   fs.mkdirSync(CACHE_DIR, { recursive: true });
 }
 }
 
 
-const agent = new Agent({ allowH2: true });
+const agent = new Agent({ allowH2: false });
 
 
 setGlobalDispatcher(agent.compose(
 setGlobalDispatcher(agent.compose(
   interceptors.retry({
   interceptors.retry({

+ 8 - 4
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([
 const kwfilter = createKeywordFilter([
   '!',
   '!',
   '?',
   '?',
@@ -132,10 +130,16 @@ const kwfilter = createKeywordFilter([
   // special modifier
   // special modifier
   '$popup',
   '$popup',
   '$removeparam',
   '$removeparam',
+  '$redirect',
   '$popunder',
   '$popunder',
   '$cname',
   '$cname',
   '$frame',
   '$frame',
   '$domain',
   '$domain',
+  '$from',
+  '$to',
+  '$csp',
+  '$replace',
+  '$urlskip',
   // some bad syntax
   // some bad syntax
   '^popup'
   '^popup'
 ]);
 ]);

+ 26 - 15
Build/lib/trie.test.ts

@@ -91,8 +91,7 @@ describe('Trie', () => {
     trie.add('example.com');
     trie.add('example.com');
     trie.add('moe.sb');
     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('example.org')).toBe(false);
 
 
     expect(trie.delete('skk.moe')).toBe(true);
     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('.example.com')).toStrictEqual(['blog.example.com', 'cdn.example.com']);
     expect(trie.find('org')).toStrictEqual(['example.org']);
     expect(trie.find('org')).toStrictEqual(['example.org']);
     expect(trie.find('example.net')).toStrictEqual([]);
     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', () => {
   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('.example.com')).toStrictEqual(['.example.com']);
     expect(trie.find('org')).toStrictEqual(['example.org']);
     expect(trie.find('org')).toStrictEqual(['example.org']);
     expect(trie.find('example.net')).toStrictEqual([]);
     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.', () => {
   it('should be possible to create a trie from an arbitrary iterable.', () => {
@@ -192,6 +191,28 @@ describe('surge domainset dedupe', () => {
 });
 });
 
 
 describe('smol tree', () => {
 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', () => {
   it('should create simple tree - 1', () => {
     const trie = createTrie([
     const trie = createTrie([
       '.skk.moe', 'blog.skk.moe', '.cdn.skk.moe', 'skk.moe',
       '.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([
     const trie = createTrie([
       'skk.moe',
       'skk.moe',
       'anotherskk.moe',
       'anotherskk.moe',
@@ -275,16 +296,6 @@ describe('smol tree', () => {
       'img.skk.local'
       'img.skk.local'
     ], true);
     ], 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');
     trie.whitelist('.skk.moe');
 
 
     expect(trie.dump()).toStrictEqual([
     expect(trie.dump()).toStrictEqual([

+ 42 - 25
Build/lib/trie.ts

@@ -41,7 +41,7 @@ function deepTrieNodeToJSON(node: TrieNode,
 
 
 const createNode = <Meta = any>(parent: TrieNode | null = null): TrieNode => [1, parent, new Map<string, TrieNode>(), null] as TrieNode<Meta>;
 const createNode = <Meta = any>(parent: TrieNode | null = null): TrieNode => [1, parent, new Map<string, TrieNode>(), null] as TrieNode<Meta>;
 
 
-export function hostnameToTokens(hostname: string, hostnameFromIndex: number): string[] {
+function hostnameToTokens(hostname: string, hostnameFromIndex: number): string[] {
   const tokens = hostname.split('.');
   const tokens = hostname.split('.');
   const results: string[] = [];
   const results: string[] = [];
   let token = '';
   let token = '';
@@ -50,6 +50,8 @@ export function hostnameToTokens(hostname: string, hostnameFromIndex: number): s
     token = tokens[i];
     token = tokens[i];
     if (token.length > 0) {
     if (token.length > 0) {
       results.push(token);
       results.push(token);
+    } else {
+      throw new TypeError(JSON.stringify({ hostname, hostnameFromIndex }, null, 2));
     }
     }
   }
   }
 
 
@@ -117,7 +119,9 @@ abstract class Triebase<Meta = any> {
     let parent: TrieNode = node;
     let parent: TrieNode = node;
 
 
     let token: string;
     let token: string;
+    let child: Map<string, TrieNode<Meta>> = node[2];
 
 
+    // reverse lookup from end to start
     for (let i = tokens.length - 1; i >= 0; i--) {
     for (let i = tokens.length - 1; i >= 0; i--) {
       token = tokens[i];
       token = tokens[i];
 
 
@@ -127,8 +131,10 @@ abstract class Triebase<Meta = any> {
 
 
       parent = node;
       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 {
       } else {
         return null;
         return null;
       }
       }
@@ -147,6 +153,8 @@ abstract class Triebase<Meta = any> {
     let node: TrieNode = this.$root;
     let node: TrieNode = this.$root;
     let parent: TrieNode = node;
     let parent: TrieNode = node;
 
 
+    let child: Map<string, TrieNode<Meta>> = node[2];
+
     const onToken = (token: string) => {
     const onToken = (token: string) => {
       // if (token === '') {
       // if (token === '') {
       //   return true;
       //   return true;
@@ -154,8 +162,10 @@ abstract class Triebase<Meta = any> {
 
 
       parent = node;
       parent = node;
 
 
-      if (node[2].has(token)) {
-        node = node[2].get(token)!;
+      child = node[2];
+
+      if (child.has(token)) {
+        node = child.get(token)!;
       } else {
       } else {
         return null;
         return null;
       }
       }
@@ -204,12 +214,14 @@ abstract class Triebase<Meta = any> {
     const node = nodeStack.shift()!;
     const node = nodeStack.shift()!;
     const suffix = suffixStack.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++) {
       for (let i = 0, l = keys.length; i < l; i++) {
         const key = keys[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
         // Pushing the child node to the stack for next iteration of DFS
         nodeStack.push(childNode);
         nodeStack.push(childNode);
@@ -271,17 +283,18 @@ abstract class Triebase<Meta = any> {
     suffixStack.push(initialSuffix);
     suffixStack.push(initialSuffix);
 
 
     let node: TrieNode<Meta> = initialNode;
     let node: TrieNode<Meta> = initialNode;
+    let child: Map<string, TrieNode<Meta>> = node[2];
 
 
     do {
     do {
       node = nodeStack.shift()!;
       node = nodeStack.shift()!;
       const suffix = suffixStack.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++) {
         for (let i = 0, l = keys.length; i < l; i++) {
           const key = keys[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
           // Pushing the child node to the stack for next iteration of DFS
           nodeStack.push(childNode);
           nodeStack.push(childNode);
@@ -303,18 +316,21 @@ abstract class Triebase<Meta = any> {
     const onLoop = (node: TrieNode, parent: TrieNode, token: string) => {
     const onLoop = (node: TrieNode, parent: TrieNode, token: string) => {
       // Keeping track of a potential branch to prune
       // 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];
+
+      // console.log({
+      //   child, parent, token
+      // });
+      // console.log(this.inspect(0));
 
 
-      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
+      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;
           toPrune = null;
           tokenToPrune = 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
         // 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;
@@ -552,7 +568,6 @@ export class HostnameSmolTrie<Meta = any> extends Triebase<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) {
     const tokens = hostnameToTokens(suffix, hostnameFromIndex);
     const tokens = hostnameToTokens(suffix, hostnameFromIndex);
     const res = this.getSingleChildLeaf(tokens);
     const res = this.getSingleChildLeaf(tokens);
-
     if (res === null) return;
     if (res === null) return;
 
 
     const { node, toPrune, tokenToPrune } = res;
     const { node, toPrune, tokenToPrune } = res;
@@ -572,7 +587,7 @@ export class HostnameSmolTrie<Meta = any> extends Triebase<Meta> {
     // return early if not found
     // return early if not found
     if (missingBit(node[0], START)) return;
     if (missingBit(node[0], START)) return;
 
 
-    if (tokenToPrune && toPrune) {
+    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);
@@ -587,13 +602,15 @@ export class HostnameTrie<Meta = any> extends Triebase<Meta> {
 
 
   add(suffix: string, includeAllSubdomain = suffix[0] === '.', meta?: Meta, hostnameFromIndex = suffix[0] === '.' ? 1 : 0): void {
   add(suffix: string, includeAllSubdomain = suffix[0] === '.', meta?: Meta, hostnameFromIndex = suffix[0] === '.' ? 1 : 0): void {
     let node: TrieNode<Meta> = this.$root;
     let node: TrieNode<Meta> = this.$root;
+    let child: Map<string, TrieNode<Meta>> = node[2];
 
 
     const onToken = (token: string) => {
     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 {
       } else {
         const newNode = createNode(node);
         const newNode = createNode(node);
-        node[2].set(token, newNode);
+        child.set(token, newNode);
         node = newNode;
         node = newNode;
       }
       }
 
 

+ 1 - 1
Build/trace/index.ts

@@ -120,7 +120,7 @@ export function task(importMetaMain: boolean, importMetaPath: string) {
       dummySpan.traceChildAsync('dummy', (childSpan) => fn(childSpan, onCleanup)).finally(() => {
       dummySpan.traceChildAsync('dummy', (childSpan) => fn(childSpan, onCleanup)).finally(() => {
         dummySpan.stop();
         dummySpan.stop();
         printTraceResult(dummySpan.traceResult);
         printTraceResult(dummySpan.traceResult);
-        whyIsNodeRunning();
+        process.nextTick(whyIsNodeRunning);
       });
       });
     }
     }