Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Perf - Try improving suggestion performance. #1639

Merged
merged 2 commits into from Sep 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,5 +1,5 @@
import { compare } from './Comparable';
import { __testing__, SortedQueue } from './SortedQueue';
import { __testing__, MinHeapQueue } from './MinHeapQueue';

const { addToHeap, takeFromHeap } = __testing__;

Expand All @@ -25,7 +25,7 @@ describe('Validate Mere Sort methods', () => {
${'abc def ghi jkl mno pqr stu vwx yz'}
${'aaaaaaaaaaaaaaaaaa'}
`('Merge Queue $letters', ({ letters }: { letters: string }) => {
const q = new SortedQueue<string>(compare);
const q = new MinHeapQueue<string>(compare);
const values = letters.split('');
q.concat(values);
expect(q.length).toBe(values.length);
Expand All @@ -35,7 +35,7 @@ describe('Validate Mere Sort methods', () => {
});

test('Queue', () => {
const q = new SortedQueue<string>(compare);
const q = new MinHeapQueue<string>(compare);
expect(q.length).toBe(0);
q.add('one');
q.add('two');
Expand All @@ -60,13 +60,13 @@ describe('Validate Mere Sort methods', () => {
];

const sorted = values.concat().sort(compare);
const q = new SortedQueue<number>(compare);
const q = new MinHeapQueue<number>(compare);
q.concat(values);
expect([...q]).toEqual(sorted);
});

test('Queue Random', () => {
const q = new SortedQueue<number>(compare);
const q = new MinHeapQueue<number>(compare);
for (let i = 0; i < 100; ++i) {
const s = Math.random();
const n = Math.floor(100 * s);
Expand All @@ -83,7 +83,7 @@ describe('Validate Mere Sort methods', () => {
});

test('Clone', () => {
const q = new SortedQueue<number>(compare);
const q = new MinHeapQueue<number>(compare);
for (let i = 0; i < 10; ++i) {
const s = Math.random();
const n = Math.floor(100 * s);
Expand Down
Expand Up @@ -46,11 +46,14 @@ function takeFromHeap<T>(t: T[], compare: (a: T, b: T) => number): T | undefined
return result;
}

export class SortedQueue<T> implements IterableIterator<T> {
/**
* MinHeapQueue - based upon a minHeap array.
*/
export class MinHeapQueue<T> implements IterableIterator<T> {
private values: T[] = [];
constructor(readonly compare: (a: T, b: T) => number) {}

add(t: T): SortedQueue<T> {
add(t: T): MinHeapQueue<T> {
addToHeap(this.values, t, this.compare);
return this;
}
Expand All @@ -63,7 +66,7 @@ export class SortedQueue<T> implements IterableIterator<T> {
return takeFromHeap(this.values, this.compare);
}

concat(i: Iterable<T>): SortedQueue<T> {
concat(i: Iterable<T>): MinHeapQueue<T> {
for (const v of i) {
this.add(v);
}
Expand All @@ -86,8 +89,8 @@ export class SortedQueue<T> implements IterableIterator<T> {
return this;
}

clone(): SortedQueue<T> {
const clone = new SortedQueue(this.compare);
clone(): MinHeapQueue<T> {
const clone = new MinHeapQueue(this.compare);
clone.values = this.values.concat();
return clone;
}
Expand Down
51 changes: 51 additions & 0 deletions packages/cspell-lib/src/util/PairingHeap.test.ts
@@ -0,0 +1,51 @@
import { PairingHeap } from './PairingHeap';

describe('PairingHeap', () => {
test('Basic add and remove', () => {
const compare = new Intl.Collator().compare;
const values = ['one', 'two', 'three', 'four', 'five', 'six', 'seven'];
const sorted = values.concat().sort(compare);
const heap = new PairingHeap(compare);
values.forEach((v) => heap.add(v));
expect(heap.length).toBe(values.length);
const result = [...heap];
expect(result).toEqual(sorted);
expect(heap.length).toBe(0);
});

interface Person {
name: string;
}

test('FIFO for latest', () => {
const compareStr = new Intl.Collator().compare;
const compare = (a: Person, b: Person) => compareStr(a.name, b.name);
const names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July'];
const people: Person[] = names.map((name) => ({ name }));
const sorted = people.concat().sort(compare);
const heap = new PairingHeap(compare);

heap.add(people[0]);
expect(heap.dequeue()).toBe(people[0]);
expect(heap.length).toBe(0);
expect(heap.peek()).toBeUndefined();
expect(heap.dequeue()).toBeUndefined();

heap.concat(people);
expect(heap.dequeue()).toBe(sorted[0]);
expect(heap.dequeue()).toBe(sorted[1]);
heap.concat(people);
expect(heap.dequeue()).toBe(sorted[0]);
expect(heap.dequeue()).toBe(sorted[1]);
expect(heap.peek()).toBe(sorted[2]);
expect(heap.dequeue()).toBe(sorted[2]);
expect(heap.dequeue()).toBe(sorted[2]);
expect(heap.peek()).toBe(sorted[3]);

heap.add(sorted[0]);
expect(heap.peek()).toBe(sorted[0]);
const copy = { ...sorted[0] };
// Make sure we get back the open we added.
expect(heap.add(copy).peek()).toBe(copy);
});
});
107 changes: 107 additions & 0 deletions packages/cspell-lib/src/util/PairingHeap.ts
@@ -0,0 +1,107 @@
export interface PairHeapNode<T> {
/** Value */
v: T;
/** Siblings */
s: PairHeapNode<T> | undefined;
/** Children */
c: PairHeapNode<T> | undefined;
}

export type CompareFn<T> = (a: T, b: T) => number;

export class PairingHeap<T> implements IterableIterator<T> {
private _heap: PairHeapNode<T> | undefined;
private _size = 0;

constructor(readonly compare: CompareFn<T>) {}

add(v: T): this {
this._heap = insert(this.compare, this._heap, v);
++this._size;
return this;
}

dequeue(): T | undefined {
const n = this.next();
if (n.done) return undefined;
return n.value;
}

concat(i: Iterable<T>): this {
for (const v of i) {
this.add(v);
}
return this;
}

next(): IteratorResult<T> {
if (!this._heap) {
return { value: undefined, done: true };
}
const value = this._heap.v;
--this._size;
this._heap = removeHead(this.compare, this._heap);
return { value };
}

peek(): T | undefined {
return this._heap?.v;
}

[Symbol.iterator](): IterableIterator<T> {
return this;
}

get length(): number {
return this._size;
}
}

function removeHead<T>(compare: CompareFn<T>, heap: PairHeapNode<T> | undefined): PairHeapNode<T> | undefined {
if (!heap || !heap.c) return undefined;
return mergeSiblings(compare, heap.c);
}

function insert<T>(compare: CompareFn<T>, heap: PairHeapNode<T> | undefined, v: T): PairHeapNode<T> {
const n: PairHeapNode<T> = {
v,
s: undefined,
c: undefined,
};

if (!heap || compare(v, heap.v) <= 0) {
n.c = heap;
return n;
}

n.s = heap.c;
heap.c = n;
return heap;
}

function merge<T>(compare: CompareFn<T>, a: PairHeapNode<T>, b: PairHeapNode<T>): PairHeapNode<T> {
if (compare(a.v, b.v) <= 0) {
a.s = undefined;
b.s = a.c;
a.c = b;
return a;
}
b.s = undefined;
a.s = b.c;
b.c = a;
return b;
}

function mergeSiblings<T>(compare: CompareFn<T>, n: PairHeapNode<T>): PairHeapNode<T> {
if (!n.s) return n;
const s = n.s;
const ss = s.s;
const m = merge(compare, n, s);
return ss ? merge(compare, m, mergeSiblings(compare, ss)) : m;
}

export const heapMethods = {
insert,
merge,
mergeSiblings,
};
14 changes: 7 additions & 7 deletions packages/cspell-lib/src/util/wordSplitter.ts
@@ -1,15 +1,15 @@
import { PairingHeap } from './PairingHeap';
import { escapeRegEx } from './regexHelper';
import { TextOffset } from './text';
import {
regExWordsAndDigits,
regExDanglingQuote,
regExEscapeCharacters,
regExPossibleWordBreaks,
regExSplitWords,
regExSplitWords2,
regExPossibleWordBreaks,
regExEscapeCharacters,
regExDanglingQuote,
regExTrailingEndings,
regExWordsAndDigits,
} from './textRegex';
import { SortedQueue } from './SortedQueue';
import { escapeRegEx } from './regexHelper';

const ignoreBreak: readonly number[] = Object.freeze([] as number[]);

Expand Down Expand Up @@ -388,7 +388,7 @@ function splitIntoWords(
}

let maxCost = lineSeg.relEnd - lineSeg.relStart;
const candidates = new SortedQueue<Candidate>(compare);
const candidates = new PairingHeap<Candidate>(compare);
const text = lineSeg.line.text;
candidates.concat(makeCandidates(undefined, lineSeg.relStart, 0, 0));
let attempts = 0;
Expand Down
51 changes: 51 additions & 0 deletions packages/cspell-trie-lib/src/lib/PairingHeap.test.ts
@@ -0,0 +1,51 @@
import { PairingHeap } from './PairingHeap';

describe('PairingHeap', () => {
test('Basic add and remove', () => {
const compare = new Intl.Collator().compare;
const values = ['one', 'two', 'three', 'four', 'five', 'six', 'seven'];
const sorted = values.concat().sort(compare);
const heap = new PairingHeap(compare);
values.forEach((v) => heap.add(v));
expect(heap.length).toBe(values.length);
const result = [...heap];
expect(result).toEqual(sorted);
expect(heap.length).toBe(0);
});

interface Person {
name: string;
}

test('FIFO for latest', () => {
const compareStr = new Intl.Collator().compare;
const compare = (a: Person, b: Person) => compareStr(a.name, b.name);
const names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July'];
const people: Person[] = names.map((name) => ({ name }));
const sorted = people.concat().sort(compare);
const heap = new PairingHeap(compare);

heap.add(people[0]);
expect(heap.dequeue()).toBe(people[0]);
expect(heap.length).toBe(0);
expect(heap.peek()).toBeUndefined();
expect(heap.dequeue()).toBeUndefined();

heap.concat(people);
expect(heap.dequeue()).toBe(sorted[0]);
expect(heap.dequeue()).toBe(sorted[1]);
heap.concat(people);
expect(heap.dequeue()).toBe(sorted[0]);
expect(heap.dequeue()).toBe(sorted[1]);
expect(heap.peek()).toBe(sorted[2]);
expect(heap.dequeue()).toBe(sorted[2]);
expect(heap.dequeue()).toBe(sorted[2]);
expect(heap.peek()).toBe(sorted[3]);

heap.add(sorted[0]);
expect(heap.peek()).toBe(sorted[0]);
const copy = { ...sorted[0] };
// Make sure we get back the open we added.
expect(heap.add(copy).peek()).toBe(copy);
});
});