From 9c82e5346c09e23c3e2f413e995d7ab721007424 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Sat, 14 Dec 2024 21:14:29 +0800 Subject: [PATCH] Perf: avoid mutate string in ruleset base --- Build/lib/rules/base.ts | 14 ++++----- Build/lib/rules/domainset.ts | 42 +++++++++++-------------- Build/lib/rules/ruleset.ts | 8 ++--- Build/lib/trie.test.ts | 11 ++++++- Build/lib/trie.ts | 59 ++++++++++++------------------------ 5 files changed, 57 insertions(+), 77 deletions(-) diff --git a/Build/lib/rules/base.ts b/Build/lib/rules/base.ts index d4879a03..6bafa3e8 100644 --- a/Build/lib/rules/base.ts +++ b/Build/lib/rules/base.ts @@ -100,14 +100,14 @@ export abstract class RuleOutput { for (let i = 0, len = domains.length; i < len; i++) { d = domains[i]; if (d !== null) { - this.addDomain(d); + this.domainTrie.add(d, false, null, 0); } } return this; } - addDomainSuffix(domain: string) { - this.domainTrie.add(domain, true); + addDomainSuffix(domain: string, lineFromDot = domain[0] === '.') { + this.domainTrie.add(domain, true, lineFromDot ? 1 : 0); return this; } @@ -126,9 +126,9 @@ export abstract class RuleOutput { private async addFromDomainsetPromise(source: AsyncIterable | Iterable | string[]) { for await (const line of source) { if (line[0] === '.') { - this.addDomainSuffix(line); + this.addDomainSuffix(line, true); } else { - this.addDomain(line); + this.domainTrie.add(line, false, null, 0); } } } @@ -147,10 +147,10 @@ export abstract class RuleOutput { switch (type) { case 'DOMAIN': - this.addDomain(value); + this.domainTrie.add(value, false, null, 0); break; case 'DOMAIN-SUFFIX': - this.addDomainSuffix(value); + this.addDomainSuffix(value, false); break; case 'DOMAIN-KEYWORD': this.addDomainKeyword(value); diff --git a/Build/lib/rules/domainset.ts b/Build/lib/rules/domainset.ts index e4eafcfe..f681225a 100644 --- a/Build/lib/rules/domainset.ts +++ b/Build/lib/rules/domainset.ts @@ -8,55 +8,47 @@ import { looseTldtsOpt } from '../../constants/loose-tldts-opt'; import { fastStringCompare } from '../misc'; import escapeStringRegexp from 'escape-string-regexp-node'; -type Preprocessed = string[]; - -export class DomainsetOutput extends RuleOutput { +export class DomainsetOutput extends RuleOutput { protected type = 'domainset' as const; + private $surge: string[] = ['this_ruleset_is_made_by_sukkaw.ruleset.skk.moe']; + private $clash: string[] = ['this_ruleset_is_made_by_sukkaw.ruleset.skk.moe']; + private $singbox_domains: string[] = ['this_ruleset_is_made_by_sukkaw.ruleset.skk.moe']; + private $singbox_domains_suffixes: string[] = ['this_ruleset_is_made_by_sukkaw.ruleset.skk.moe']; + preprocess() { const kwfilter = createKeywordFilter(this.domainKeywords); - const results: string[] = []; - this.domainTrie.dump((domain) => { + this.domainTrie.dumpWithoutDot((domain, subdomain) => { if (kwfilter(domain)) { return; } + + this.$surge.push(subdomain ? '.' + domain : domain); + this.$clash.push(subdomain ? `+.${domain}` : domain); + (subdomain ? this.$singbox_domains : this.$singbox_domains_suffixes).push(domain); + results.push(domain); }, true); - const sorted = results; - sorted.push('this_ruleset_is_made_by_sukkaw.ruleset.skk.moe'); - - return sorted; + return results; } surge(): string[] { - return this.$preprocessed; + return this.$surge; } clash(): string[] { - return this.$preprocessed.map(i => (i[0] === '.' ? `+${i}` : i)); + return this.$clash; } singbox(): string[] { - const domains: string[] = []; - const domainSuffixes: string[] = []; - - for (let i = 0, len = this.$preprocessed.length; i < len; i++) { - const domain = this.$preprocessed[i]; - if (domain[0] === '.') { - domainSuffixes.push(domain.slice(1)); - } else { - domains.push(domain); - } - } - return RuleOutput.jsonToLines({ version: 2, rules: [{ - domain: domains, - domain_suffix: domainSuffixes + domain: this.$singbox_domains, + domain_suffix: this.$singbox_domains_suffixes }] } satisfies SingboxSourceFormat); } diff --git a/Build/lib/rules/ruleset.ts b/Build/lib/rules/ruleset.ts index f48ed549..df50df5c 100644 --- a/Build/lib/rules/ruleset.ts +++ b/Build/lib/rules/ruleset.ts @@ -23,13 +23,13 @@ export class RulesetOutput extends RuleOutput { const domainSuffixes: string[] = []; const sortedDomainRules: string[] = []; - this.domainTrie.dump((domain) => { + this.domainTrie.dumpWithoutDot((domain, includeAllSubdomain) => { if (kwfilter(domain)) { return; } - if (domain[0] === '.') { - domainSuffixes.push(domain.slice(1)); - sortedDomainRules.push(`DOMAIN-SUFFIX,${domain.slice(1)}`); + if (includeAllSubdomain) { + domainSuffixes.push(domain); + sortedDomainRules.push(`DOMAIN-SUFFIX,${domain}`); } else { domains.push(domain); sortedDomainRules.push(`DOMAIN,${domain}`); diff --git a/Build/lib/trie.test.ts b/Build/lib/trie.test.ts index 6e5476cb..25fede25 100644 --- a/Build/lib/trie.test.ts +++ b/Build/lib/trie.test.ts @@ -1,6 +1,15 @@ -import { createTrie } from './trie'; import { describe, it } from 'mocha'; import { expect } from 'expect'; +import { HostnameSmolTrie, HostnameTrie } from './trie'; + +function createTrie(from: string[] | Set | null, smolTree: true): HostnameSmolTrie; +function createTrie(from?: string[] | Set | null, smolTree?: false): HostnameTrie; +function createTrie<_Meta = any>(from?: string[] | Set | null, smolTree = true) { + if (smolTree) { + return new HostnameSmolTrie(from); + } + return new HostnameTrie(from); +}; // describe('hostname to tokens', () => { // it('should split hostname into tokens.', () => { diff --git a/Build/lib/trie.ts b/Build/lib/trie.ts index 005427f6..31fb9504 100644 --- a/Build/lib/trie.ts +++ b/Build/lib/trie.ts @@ -170,10 +170,8 @@ abstract class Triebase { }; public contains(suffix: string, includeAllSubdomain = suffix[0] === '.'): boolean { - let hostnameFromIndex = 0; - if (suffix[0] === '.') { - hostnameFromIndex = 1; - } + const hostnameFromIndex = suffix[0] === '.' ? 1 : 0; + const res = this.walkIntoLeafWithSuffix(suffix, hostnameFromIndex); if (!res) return false; if (includeAllSubdomain) return res.node[1]; @@ -333,13 +331,9 @@ abstract class Triebase { public find( inputSuffix: string, subdomainOnly = inputSuffix[0] === '.', - hostnameFromIndex = 0 + hostnameFromIndex = inputSuffix[0] === '.' ? 1 : 0 // /** @default true */ includeEqualWithSuffix = true ): string[] { - if (inputSuffix[0] === '.') { - hostnameFromIndex = 1; - } - const inputTokens = hostnameToTokens(inputSuffix, hostnameFromIndex); const res = this.walkIntoLeafWithTokens(inputTokens); if (res === null) return []; @@ -395,11 +389,7 @@ abstract class Triebase { * Method used to assert whether the given prefix exists in the Trie. */ public has(suffix: string, includeAllSubdomain = suffix[0] === '.'): boolean { - let hostnameFromIndex = 0; - - if (suffix[0] === '.') { - hostnameFromIndex = 1; - } + const hostnameFromIndex = suffix[0] === '.' ? 1 : 0; const res = this.walkIntoLeafWithSuffix(suffix, hostnameFromIndex); @@ -409,6 +399,18 @@ abstract class Triebase { return true; }; + public dumpWithoutDot(onSuffix: (suffix: string, subdomain: boolean) => void, withSort = false) { + const handleSuffix = (suffix: string[], subdomain: boolean) => { + onSuffix(fastStringArrayJoin(suffix, '.'), subdomain); + }; + + if (withSort) { + this.walkWithSort(handleSuffix); + } else { + this.walk(handleSuffix); + } + } + public dump(onSuffix: (suffix: string) => void, withSort?: boolean): void; public dump(onSuffix?: null, withSort?: boolean): string[]; public dump(onSuffix?: ((suffix: string) => void) | null, withSort = false): string[] | void { @@ -490,14 +492,10 @@ abstract class Triebase { export class HostnameSmolTrie extends Triebase { public smolTree = true; - add(suffix: string, includeAllSubdomain = suffix[0] === '.', meta?: Meta, hostnameFromIndex = 0): void { + add(suffix: string, includeAllSubdomain = suffix[0] === '.', meta?: Meta, hostnameFromIndex = suffix[0] === '.' ? 1 : 0): void { let node: TrieNode = this.$root; let curNodeChildren: Map> = node[3]; - if (hostnameFromIndex === 0 && suffix[0] === '.') { - hostnameFromIndex = 1; - } - const onToken = (token: string) => { curNodeChildren = node[3]; if (curNodeChildren.has(token)) { @@ -544,11 +542,7 @@ export class HostnameSmolTrie extends Triebase { node[4] = meta!; } - public whitelist(suffix: string, includeAllSubdomain = suffix[0] === '.', hostnameFromIndex = 0) { - if (suffix[0] === '.') { - hostnameFromIndex = 1; - } - + public whitelist(suffix: string, includeAllSubdomain = suffix[0] === '.', hostnameFromIndex = suffix[0] === '.' ? 1 : 0) { const tokens = hostnameToTokens(suffix, hostnameFromIndex); const res = this.getSingleChildLeaf(tokens); @@ -584,7 +578,7 @@ export class HostnameTrie extends Triebase { return this.$size; } - add(suffix: string, includeAllSubdomain = suffix[0] === '.', meta?: Meta, hostnameFromIndex = 0): void { + add(suffix: string, includeAllSubdomain = suffix[0] === '.', meta?: Meta, hostnameFromIndex = suffix[0] === '.' ? 1 : 0): void { let node: TrieNode = this.$root; const onToken = (token: string) => { @@ -599,10 +593,6 @@ export class HostnameTrie extends Triebase { return false; }; - if (hostnameFromIndex === 0 && suffix[0] === '.') { - hostnameFromIndex = 1; - } - // When walkHostnameTokens returns true, we should skip the rest if (walkHostnameTokens(suffix, onToken, hostnameFromIndex)) { return; @@ -620,17 +610,6 @@ export class HostnameTrie extends Triebase { } } -export function createTrie(from: string[] | Set | null, smolTree: true): HostnameSmolTrie; -export function createTrie(from?: string[] | Set | null, smolTree?: false): HostnameTrie; -export function createTrie<_Meta = any>(from?: string[] | Set | null, smolTree = true) { - if (smolTree) { - return new HostnameSmolTrie(from); - } - return new HostnameTrie(from); -}; - -export type Trie = ReturnType; - // function deepEqualArray(a: string[], b: string[]) { // let len = a.length; // if (len !== b.length) return false;