Perf: avoid mutate string in ruleset base

This commit is contained in:
SukkaW 2024-12-14 21:14:29 +08:00
parent 5c85a0c504
commit 9c82e5346c
5 changed files with 57 additions and 77 deletions

View File

@ -100,14 +100,14 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
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<TPreprocessed = unknown> {
private async addFromDomainsetPromise(source: AsyncIterable<string> | Iterable<string> | 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<TPreprocessed = unknown> {
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);

View File

@ -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<Preprocessed> {
export class DomainsetOutput extends RuleOutput<string[]> {
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);
}

View File

@ -23,13 +23,13 @@ export class RulesetOutput extends RuleOutput<Preprocessed> {
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}`);

View File

@ -1,6 +1,15 @@
import { createTrie } from './trie';
import { describe, it } from 'mocha';
import { expect } from 'expect';
import { HostnameSmolTrie, HostnameTrie } from './trie';
function createTrie<Meta = any>(from: string[] | Set<string> | null, smolTree: true): HostnameSmolTrie<Meta>;
function createTrie<Meta = any>(from?: string[] | Set<string> | null, smolTree?: false): HostnameTrie<Meta>;
function createTrie<_Meta = any>(from?: string[] | Set<string> | null, smolTree = true) {
if (smolTree) {
return new HostnameSmolTrie(from);
}
return new HostnameTrie(from);
};
// describe('hostname to tokens', () => {
// it('should split hostname into tokens.', () => {

View File

@ -170,10 +170,8 @@ abstract class Triebase<Meta = any> {
};
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<Meta = any> {
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<Meta = any> {
* 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<Meta = any> {
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<Meta = any> {
export class HostnameSmolTrie<Meta = any> extends Triebase<Meta> {
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<Meta> = this.$root;
let curNodeChildren: Map<string, TrieNode<Meta>> = 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<Meta = any> extends Triebase<Meta> {
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<Meta = any> extends Triebase<Meta> {
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<Meta> = this.$root;
const onToken = (token: string) => {
@ -599,10 +593,6 @@ export class HostnameTrie<Meta = any> extends Triebase<Meta> {
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<Meta = any> extends Triebase<Meta> {
}
}
export function createTrie<Meta = any>(from: string[] | Set<string> | null, smolTree: true): HostnameSmolTrie<Meta>;
export function createTrie<Meta = any>(from?: string[] | Set<string> | null, smolTree?: false): HostnameTrie<Meta>;
export function createTrie<_Meta = any>(from?: string[] | Set<string> | null, smolTree = true) {
if (smolTree) {
return new HostnameSmolTrie(from);
}
return new HostnameTrie(from);
};
export type Trie = ReturnType<typeof createTrie>;
// function deepEqualArray(a: string[], b: string[]) {
// let len = a.length;
// if (len !== b.length) return false;