Skip to content

Commit

Permalink
Improve pipe performance
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S committed Jul 30, 2024
1 parent cb14fe2 commit 14ec937
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 50 deletions.
12 changes: 8 additions & 4 deletions packages/cspell-pipe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,21 @@
"!**/*.tsbuildInfo",
"!**/__mocks__",
"!**/*.spec.*",
"!**/*.perf.*",
"!**/*.test.*",
"!**/perf/**",
"!**/test/**",
"!**/*.map"
],
"scripts": {
"build": "tsc -b . -f",
"watch": "tsc -b . -w -f",
"build": "tsc -p .",
"watch": "tsc -p . -w",
"clean": "shx rm -rf dist temp coverage \"*.tsbuildInfo\"",
"clean-build": "pnpm run clean && pnpm run build",
"coverage": "vitest run --coverage",
"test-watch": "vitest",
"test": "vitest run"
"test": "vitest run",
"test:perf": "NODE_ENV=production insight --register ts-node/esm --file \"**/*.perf.{mts,ts}\""
},
"repository": {
"type": "git",
Expand All @@ -124,6 +127,7 @@
"node": ">=18"
},
"devDependencies": {
"globby": "^14.0.2"
"globby": "^14.0.2",
"perf-insight": "^1.2.0"
}
}
8 changes: 4 additions & 4 deletions packages/cspell-pipe/src/operators/append.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import type { PipeFn } from '../internalTypes.js';
export function opAppendAsync<T>(
...iterablesToAppend: (AsyncIterable<T> | Iterable<T>)[]
): (iter: AsyncIterable<T> | Iterable<T>) => AsyncIterable<T> {
async function* fn(iter: AsyncIterable<T> | Iterable<T>) {
async function* fnAppend(iter: AsyncIterable<T> | Iterable<T>) {
yield* iter;
for (const i of iterablesToAppend) {
yield* i;
}
}

return fn;
return fnAppend;
}

/**
Expand All @@ -25,14 +25,14 @@ export function opAppendAsync<T>(
* @returns
*/
export function opAppendSync<T>(...iterablesToAppend: Iterable<T>[]): (iter: Iterable<T>) => Iterable<T> {
function* fn(iter: Iterable<T>) {
function* fnAppend(iter: Iterable<T>) {
yield* iter;
for (const i of iterablesToAppend) {
yield* i;
}
}

return fn;
return fnAppend;
}

export function opAppend<T>(...iterablesToAppend: Iterable<T>[]): PipeFn<T, T> {
Expand Down
8 changes: 4 additions & 4 deletions packages/cspell-pipe/src/operators/buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { PipeFn } from '../internalTypes.js';
* @returns A function that takes an async iterable and returns an async iterable of arrays of the given size.
*/
export function opBufferAsync<T>(size: number): (iter: AsyncIterable<T>) => AsyncIterable<T[]> {
async function* fn(iter: Iterable<T> | AsyncIterable<T>) {
async function* fnBuffer(iter: Iterable<T> | AsyncIterable<T>) {
let buffer: T[] = [];
for await (const v of iter) {
buffer.push(v);
Expand All @@ -22,15 +22,15 @@ export function opBufferAsync<T>(size: number): (iter: AsyncIterable<T>) => Asyn
}
}

return fn;
return fnBuffer;
}

/**
* @param size - The size of the buffer.
* @returns A function that takes an iterable and returns an iterable of arrays of the given size.
*/
export function opBufferSync<T>(size: number): (iter: Iterable<T>) => Iterable<T[]> {
function* fn(iter: Iterable<T>) {
function* fnBuffer(iter: Iterable<T>) {
let buffer: T[] = [];
for (const v of iter) {
buffer.push(v);
Expand All @@ -45,7 +45,7 @@ export function opBufferSync<T>(size: number): (iter: Iterable<T>) => Iterable<T
}
}

return fn;
return fnBuffer;
}

export function opBuffer<T>(size: number): PipeFn<T, T[]> {
Expand Down
4 changes: 3 additions & 1 deletion packages/cspell-pipe/src/operators/concatMap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, expect, test } from 'vitest';

import { toArray } from '../helpers/index.js';
import { pipeAsync, pipeSync } from '../pipe.js';
import { opConcatMap } from './concatMap.js';
import { _opConcatMapSync, opConcatMap } from './concatMap.js';

describe('Validate map', () => {
test('map', async () => {
Expand All @@ -17,11 +17,13 @@ describe('Validate map', () => {

const s = pipeSync(values, mapToLen, opConcatMap(mapFn2));
const a = pipeAsync(values, mapToLen, opConcatMap(mapFn2));
const s2 = pipeSync(values, mapToLen, _opConcatMapSync(mapFn2));

const sync = toArray(s);
const async = await toArray(a);

expect(sync).toEqual(expected);
expect(async).toEqual(expected);
expect([...s2]).toEqual(expected);
});
});
36 changes: 34 additions & 2 deletions packages/cspell-pipe/src/operators/concatMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,44 @@ export function opConcatMapAsync<T, U = T>(
}

export function opConcatMapSync<T, U = T>(mapFn: (v: T) => Iterable<U>): (iter: Iterable<T>) => Iterable<U> {
function* fn(iter: Iterable<T>) {
function fnConcatMapSync(iterable: Iterable<T>): Iterable<U> {
function opConcatMapIterator() {
const iter = iterable[Symbol.iterator]();
let resultsIter: Iterator<U> | undefined = undefined;
function nextConcatMap() {
while (true) {
if (resultsIter) {
const { done, value } = resultsIter.next();
if (!done) {
return { value };
}
resultsIter = undefined;
}
const { done, value } = iter.next();
if (done) {
return { done, value: undefined };
}
resultsIter = mapFn(value)[Symbol.iterator]();
}
}
return {
next: nextConcatMap,
};
}
return {
[Symbol.iterator]: opConcatMapIterator,
};
}
return fnConcatMapSync;
}

export function _opConcatMapSync<T, U = T>(mapFn: (v: T) => Iterable<U>): (iter: Iterable<T>) => Iterable<U> {
function* fnConcatMapSync(iter: Iterable<T>) {
for (const v of iter) {
yield* mapFn(v);
}
}
return fn;
return fnConcatMapSync;
}

export const opConcatMap = <T, U>(fn: (v: T) => Iterable<U>) => toPipeFn(opConcatMapSync(fn), opConcatMapAsync(fn));
3 changes: 2 additions & 1 deletion packages/cspell-pipe/src/operators/filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, expect, test } from 'vitest';

import { toArray, toAsyncIterable } from '../helpers/index.js';
import { pipeAsync, pipeSync } from '../pipe.js';
import { opFilter, opFilterAsync } from './filter.js';
import { _opFilterSync, opFilter, opFilterAsync } from './filter.js';

describe('Validate filter', () => {
test('filter', async () => {
Expand All @@ -22,6 +22,7 @@ describe('Validate filter', () => {

expect(sync).toEqual(expected);
expect(async).toEqual(expected);
expect([..._opFilterSync(filterFn)(values)]).toEqual(expected);
});

type Primitives = string | number | boolean;
Expand Down
32 changes: 28 additions & 4 deletions packages/cspell-pipe/src/operators/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,50 @@ export function opFilterAsync<T>(filterFn: (v: Awaited<T>) => boolean): (iter: A
export function opFilterAsync<T>(filterFn: (v: Awaited<T>) => Promise<boolean>): (iter: AsyncIterable<T>) => AsyncIterable<Awaited<T>>;
// prettier-ignore
export function opFilterAsync<T>(filterFn: (v: Awaited<T>) => boolean | Promise<boolean>): (iter: AsyncIterable<T>) => AsyncIterable<Awaited<T>> {
async function* fn(iter: Iterable<T> | AsyncIterable<T>) {
async function* genFilter(iter: Iterable<T> | AsyncIterable<T>) {
for await (const v of iter) {
const pass = await filterFn(v);
if (pass) yield v;
}
}

return fn;
return genFilter;
}

export function opFilterSync<T, S extends T>(filterFn: (v: T) => v is S): (iter: Iterable<T>) => Iterable<S>;
export function opFilterSync<T>(filterFn: (v: T) => boolean): (iter: Iterable<T>) => Iterable<T>;
export function opFilterSync<T>(filterFn: (v: T) => boolean): (iter: Iterable<T>) => Iterable<T> {
function* fn(iter: Iterable<T>) {
function opFilterIterable(iterable: Iterable<T>) {
function opFilterIterator() {
const iter = iterable[Symbol.iterator]();
function nextOpFilter() {
while (true) {
const { done, value } = iter.next();
if (done) return { done, value: undefined };
if (filterFn(value)) return { value };
}
}
return {
next: nextOpFilter,
};
}
return {
[Symbol.iterator]: opFilterIterator,
};
}
return opFilterIterable;
}

export function _opFilterSync<T, S extends T>(filterFn: (v: T) => v is S): (iter: Iterable<T>) => Iterable<S>;
export function _opFilterSync<T>(filterFn: (v: T) => boolean): (iter: Iterable<T>) => Iterable<T>;
export function _opFilterSync<T>(filterFn: (v: T) => boolean): (iter: Iterable<T>) => Iterable<T> {
function* genFilter(iter: Iterable<T>) {
for (const v of iter) {
if (filterFn(v)) yield v;
}
}

return fn;
return genFilter;
}

export function opFilter<T, S extends T>(fn: (v: T) => v is S): PipeFn<T, S>;
Expand Down
4 changes: 3 additions & 1 deletion packages/cspell-pipe/src/operators/map.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, expect, test } from 'vitest';

import { toArray } from '../helpers/index.js';
import { pipeAsync, pipeSync } from '../pipe.js';
import { opMap } from './map.js';
import { _opMapSync, opMap } from './map.js';

describe('Validate map', () => {
test('map', async () => {
Expand All @@ -17,11 +17,13 @@ describe('Validate map', () => {

const s = pipeSync(values, mapToLen, opMap(mapFn2));
const a = pipeAsync(values, mapToLen, opMap(mapFn2));
const sGen = pipeSync(values, mapToLen, _opMapSync(mapFn2));

const sync = toArray(s);
const async = await toArray(a);

expect(sync).toEqual(expected);
expect(async).toEqual(expected);
expect(toArray(sGen)).toEqual(expected);
});
});
30 changes: 25 additions & 5 deletions packages/cspell-pipe/src/operators/map.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
import { toPipeFn } from '../helpers/util.js';

export function opMapAsync<T, U = T>(mapFn: (v: T) => U): (iter: AsyncIterable<T>) => AsyncIterable<U> {
async function* fn(iter: Iterable<T> | AsyncIterable<T>) {
async function* genMap(iter: Iterable<T> | AsyncIterable<T>) {
for await (const v of iter) {
yield mapFn(v);
}
}

return fn;
return genMap;
}

export function opMapSync<T, U = T>(mapFn: (v: T) => U): (iter: Iterable<T>) => Iterable<U> {
function* fn(iter: Iterable<T>) {
export function _opMapSync<T, U = T>(mapFn: (v: T) => U): (iter: Iterable<T>) => Iterable<U> {
function* genMap(iter: Iterable<T>) {
for (const v of iter) {
yield mapFn(v);
}
}
return fn;
return genMap;
}

export function opMapSync<T, U = T>(mapFn: (v: T) => U): (iterable: Iterable<T>) => Iterable<U> {
function opMapIterable(iterable: Iterable<T>) {
function opMapIterator() {
const iter = iterable[Symbol.iterator]();
function nextOpMap() {
const { done, value } = iter.next();
if (done) return { done, value: undefined };
return { value: mapFn(value) };
}
return {
next: nextOpMap,
};
}
return {
[Symbol.iterator]: opMapIterator,
};
}
return opMapIterable;
}

export const opMap = <T, U>(fn: (v: T) => U) => toPipeFn(opMapSync(fn), opMapAsync(fn));
47 changes: 47 additions & 0 deletions packages/cspell-pipe/src/perf/gen.perf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { suite } from 'perf-insight';

suite('generators vs iterators', async (test) => {
const data = Array.from({ length: 10_000 }, (_, i) => i);

function double(v: number) {
return v * 2;
}

test('generator', () => {
return testIterable(genValues(data, double));
});

test('iterator', () => {
return testIterable(iterValues(data, double));
});

function testIterable(iter: Iterable<number>) {
let sum = 0;
for (const v of iter) {
sum += v;
}
return sum;
}
});

function* genValues(i: Iterable<number>, fnMap: (v: number) => number) {
for (const v of i) {
yield fnMap(v);
}
}

function iterValues(i: Iterable<number>, fnMap: (v: number) => number): Iterable<number> {
return {
[Symbol.iterator]: () => {
const iter = i[Symbol.iterator]();
function next() {
const { done, value } = iter.next();
if (done) return { done, value: undefined };
return { value: fnMap(value) };
}
return {
next,
};
},
};
}
12 changes: 0 additions & 12 deletions packages/cspell-pipe/tsconfig.esm.json

This file was deleted.

10 changes: 8 additions & 2 deletions packages/cspell-pipe/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
{
"files": [],
"references": [{ "path": "./tsconfig.esm.json" }]
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../tsconfig.esm.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"types": ["node"]
},
"include": ["src"]
}
Loading

0 comments on commit 14ec937

Please sign in to comment.