Created
March 24, 2022 02:55
-
-
Save jeswr/f49a6975f1bfc088a9c32565e69761bf to your computer and use it in GitHub Desktop.
AsyncIterator Performance Testing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { CLOSED, ENDED } from 'asynciterator'; | |
import { ArrayIterator as OldArrayIterator } from 'asynciterator'; | |
import { ArrayIterator, AsyncIterator, range } from './asynciterator' | |
type Transform = { | |
type: 'filter'; | |
function: (elem: any) => boolean | |
} | { | |
type: 'map'; | |
function: (elem: any) => any | |
} | |
function build(tranforms: Transform[]) { | |
const rev = tranforms.reverse(); | |
return rev.slice(1, tranforms.length).reduce((f, transform) => { | |
return transform.type === 'map' ? | |
(item: any) => transform.function(f(item)) : | |
(item: any) => transform.function(item) ? f(item) : null; | |
}, rev[0].function); | |
} | |
class CompositeMapFilterVeryVeryFast<T> extends AsyncIterator<T> { | |
private tranforms: Transform[] = []; | |
private f: any; | |
constructor(private source: AsyncIterator<T>) { | |
super(); | |
source.on('readable', () => { | |
this.emit('readable'); | |
}); | |
} | |
read(): T | null { | |
const { f, source } = this | |
let item = source.read(); | |
while ((item = source.read()) !== null) { | |
item = (f as any)(item); | |
if (item !== null) | |
return item; | |
} | |
// @ts-ignore | |
if (source._state === ENDED) { | |
this._end(); | |
} | |
return item; | |
} | |
// @ts-ignore | |
filter(filter: (item: T) => boolean, self?: any): CompositeMapFilterVeryVeryFast<T> { | |
this.tranforms.push({ type: 'filter', function: filter }); | |
return this; | |
} | |
// @ts-ignore | |
map(map: (item: T) => T): CompositeMapFilterVeryVeryFast<T> { | |
this.tranforms.push({ type: 'map', function: map }); | |
return this; | |
} | |
build() { | |
this.f = build(this.tranforms); | |
} | |
} | |
class CompositeMapFilterVeryFast<T> extends AsyncIterator<T> { | |
private tranforms: Transform[] = []; | |
private f: any; | |
constructor(private source: AsyncIterator<T>) { | |
super(); | |
source.on('readable', () => { | |
this.emit('readable'); | |
}); | |
} | |
read(): T | null { | |
let item; | |
while ((item = this.source.read()) !== null) { | |
item = (this.f as any)(item); | |
if (item !== null) | |
return item; | |
} | |
// @ts-ignore | |
if (item === null && this.source._state === ENDED) { | |
this._end(); | |
} | |
return item; | |
} | |
// @ts-ignore | |
filter(filter: (item: T) => boolean, self?: any): CompositeMapFilterVeryFast<T> { | |
this.tranforms.push({ type: 'filter', function: filter }); | |
return this; | |
} | |
// @ts-ignore | |
map(map: (item: T) => T): CompositeMapFilterVeryFast<T> { | |
this.tranforms.push({ type: 'map', function: map }); | |
return this; | |
} | |
build() { | |
this.f = build(this.tranforms); | |
} | |
} | |
class CompositeMapFilterFast<T> extends AsyncIterator<T> { | |
private tranforms?: Function; | |
constructor(private source: AsyncIterator<T>) { | |
super(); | |
source.on('readable', () => { | |
this.emit('readable'); | |
}); | |
} | |
read(): T | null { | |
let item; | |
while ((item = this.source.read()) !== null) { | |
item = (this.tranforms as any)(item); | |
if (item !== null) | |
return item; | |
} | |
// @ts-ignore | |
if (item === null && this.source._state === ENDED) { | |
this._end(); | |
} | |
return item; | |
} | |
// @ts-ignore | |
filter(filter: (item: T) => boolean, self?: any): CompositeMapFilterFast<T> { | |
if (!this.tranforms) { | |
this.tranforms = (item: any) => item !== null && filter(item) ? item : null; | |
} else { | |
const { tranforms } = this | |
this.tranforms = (item: any) => { | |
const transform = (tranforms as any)(item); | |
return transform !== null && filter(transform) ? transform : null; | |
} | |
} | |
return this; | |
} | |
// @ts-ignore | |
map(map: (item: T) => T): CompositeMapFilterFast<T> { | |
if (!this.tranforms) { | |
this.tranforms = (item: any) => item !== null ? map(item) : null | |
} else { | |
const { tranforms } = this | |
this.tranforms = (item: any) => { | |
const transform = (tranforms as any)(item); | |
return transform !== null ? map(transform) : null; | |
} | |
} | |
return this; | |
} | |
} | |
class CompositeMapFilter<T> extends AsyncIterator<T> { | |
private tranforms: Transform[] = []; | |
constructor(private source: AsyncIterator<T>) { | |
super(); | |
source.on('readable', () => { | |
this.emit('readable'); | |
}); | |
} | |
read(): T | null { | |
let item = this.source.read(); | |
for (let i = 0; i < this.tranforms.length; i += 1) { | |
const transform = this.tranforms[i]; | |
switch (transform.type) { | |
case 'map': | |
item = transform.function(item); | |
case 'filter': { | |
if (!transform.function(item)) { | |
if ((item = this.source.read()) === null) | |
break; | |
else | |
i = 0 | |
} | |
} | |
} | |
} | |
// @ts-ignore | |
if (item === null && this.source._state === ENDED) { | |
this._end(); | |
} | |
return item; | |
} | |
// @ts-ignore | |
filter(filter: (item: T) => boolean, self?: any): CompositeMapFilter<T> { | |
this.tranforms.push({ type: 'filter', function: filter }); | |
return this; | |
} | |
// @ts-ignore | |
map(map: (item: T) => T): CompositeMapFilter<T> { | |
this.tranforms.push({ type: 'map', function: map }); | |
return this; | |
} | |
} | |
class MappingIterator<I, O> extends AsyncIterator<O> { | |
constructor(source: AsyncIterator<I>, map: (item: I) => O) { | |
super(); | |
source.on('readable', () => { | |
this.emit('readable'); | |
}); | |
let item: I | null; | |
this.read = (): O | null => { | |
const item = source.read(); | |
if (item === null) { | |
// @ts-ignore | |
if (source._state === ENDED || source._state === CLOSED) | |
this._end(); | |
return null | |
} | |
return map(item); | |
}; | |
} | |
} | |
class FilterIterator<I> extends AsyncIterator<I> { | |
constructor(source: AsyncIterator<I>, filter: (item: I) => boolean) { | |
super(); | |
source.on('readable', () => { | |
this.emit('readable'); | |
}); | |
let item: I | null; | |
this.read = (): I | null => { | |
let item; | |
while ((item = source.read()) !== null) { | |
if (filter(item)) | |
return item; | |
} | |
// @ts-ignore | |
if (source._state === ENDED || source._state === CLOSED) | |
this._end(); | |
return null; | |
}; | |
} | |
} | |
const generateArr = (): number[] => { | |
let i = 0; | |
return new Array(200000) | |
.fill(true) | |
.map(() => i++); | |
}; | |
const time = (createStream: (arr: number[]) => AsyncIterator<number> | CompositeMapFilter<number> | CompositeMapFilterFast<number> | CompositeMapFilterVeryFast<number> | CompositeMapFilterVeryVeryFast<number>): Promise<number> => { | |
const arr = generateArr(); | |
const str = createStream(arr); | |
let count = 0; | |
return new Promise((resolve, reject) => { | |
const now = Date.now(); | |
str.on('data', () => { | |
count += 1; | |
}) | |
.on('end', () => { | |
const then = Date.now(); | |
// if (count != arr.length / 2) { | |
// console.log(count, arr.length / 2) | |
// reject(new Error('Bad count')); | |
// return; | |
// } | |
resolve(then - now); | |
}); | |
}) | |
} | |
const main = async () => { | |
const mapArrayMethodTime = await time((arr) => { | |
let iterator: AsyncIterator<number> = new ArrayIterator(arr); | |
for (let i = 0; i < 50; i++) { | |
iterator = iterator.filter(item => item % 2 === 0); | |
iterator = iterator.map(item => item); | |
} | |
return iterator; | |
}); | |
const mapOldArrayMethodTime = await time((arr) => { | |
let iterator: any = new OldArrayIterator(arr); | |
for (let i = 0; i < 50; i++) { | |
iterator = iterator.filter((item : any) => item % 2 === 0); | |
iterator = iterator.map((item : any) => item); | |
} | |
return iterator; | |
}); | |
const mapMethodTime = await time((arr) => { | |
let iterator: AsyncIterator<number> = range(0, arr.length - 1); | |
for (let i = 0; i < 50; i++) { | |
iterator = iterator.filter(item => item % 2 === 0); | |
iterator = iterator.map(item => item); | |
} | |
return iterator; | |
}); | |
const mappingIteratorTime = await time((arr) => { | |
let iterator: AsyncIterator<number> = range(0, arr.length - 1); | |
for (let i = 0; i < 50; i++) { | |
iterator = new FilterIterator(iterator, item => item % 2 === 0); | |
iterator = new MappingIterator(iterator, item => item); | |
} | |
return iterator; | |
}); | |
const compIteratorTime = await time((arr) => { | |
let iterator: CompositeMapFilter<number> = new CompositeMapFilter(range(0, arr.length - 1)); | |
for (let i = 0; i < 50; i++) { | |
iterator = iterator.filter(item => item % 2 === 0); | |
iterator = iterator.map(item => item); | |
} | |
return iterator; | |
}); | |
const compFastIteratorTime = await time((arr) => { | |
let iterator: CompositeMapFilterFast<number> = new CompositeMapFilterFast(range(0, arr.length - 1)); | |
for (let i = 0; i < 50; i++) { | |
iterator = iterator.filter(item => item % 2 === 0); | |
iterator = iterator.map(item => item); | |
} | |
return iterator; | |
}); | |
// const compVeryFastIteratorTime = await time((arr) => { | |
// let iterator: CompositeMapFilterVeryFast<number> = new CompositeMapFilterVeryFast(range(0, arr.length - 1)); | |
// for (let i = 0; i < 50; i++) { | |
// iterator = iterator.filter(item => item % 2 === 0); | |
// iterator = iterator.map(item => item); | |
// } | |
// iterator.build(); | |
// return iterator; | |
// }); | |
const compVeryVeryFastIteratorTime = await time((arr) => { | |
let iterator: CompositeMapFilterVeryVeryFast<number> = new CompositeMapFilterVeryVeryFast(range(0, arr.length - 1)); | |
for (let i = 0; i < 50; i++) { | |
iterator = iterator.filter(item => item % 2 === 0); | |
iterator = iterator.map(item => item); | |
} | |
iterator.build(); | |
return iterator; | |
}); | |
console.log(`OldArrayIterator#map(): ${mapOldArrayMethodTime}`); | |
console.log(`ArrayIterator#map(): ${mapArrayMethodTime}`); | |
console.log(`rangeIterator#map(): ${mapMethodTime}`); | |
console.log(`MappingIterator: ${mappingIteratorTime}`); | |
console.log(`CompositeIterator: ${compIteratorTime}`); | |
console.log(`Composite'Fast'Iterator: ${compFastIteratorTime}`); | |
// console.log(`CompositeVeryFastIterator: ${compVeryFastIteratorTime}`); | |
console.log(`CompositeVeryVeryFastIterator: ${compVeryVeryFastIteratorTime}`); | |
}; | |
main().catch((err) => { | |
console.error(err); | |
process.exit(1); | |
}); |
We can also write build
using reduceRight
function build(tranforms: Transform[]) {
return tranforms.reduceRight((f, transform) => {
return transform.type === 'map' ?
(item: any) => transform.function(f(item)) :
(item: any) => transform.function(item) ? f(item) : null;
}, (e: any) => e);
}
and the best composite iterator can be written slightly more cleanly as
class CompositeMapFilterVeryVeryFast<T> extends AsyncIterator<T> {
private tranforms: ITransform[] = [];
private f: any;
constructor(private source: AsyncIterator<T>) {
super();
source.on('readable', () => {
this.emit('readable');
});
}
read(): T | null {
const { f, source } = this
let item;
while ((item = source.read()) !== null) {
if ((item = f(item)) !== null)
return item;
}
// @ts-ignore
if (source._state === ENDED) {
this._end();
}
return item;
}
// @ts-ignore
filter(filter: (item: T) => boolean): CompositeMapFilterVeryVeryFast<T> {
this.tranforms.push({ type: false, function: filter });
return this;
}
// @ts-ignore
map(map: (item: T) => T): CompositeMapFilterVeryVeryFast<T> {
this.tranforms.push({ type: true, function: map });
return this;
}
build() {
this.f = build(this.tranforms);
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
When using 5 transforms instead of 50
OldArrayIterator#map(): 3913
ArrayIterator#map(): 3939
rangeIterator#map(): 111
MappingIterator: 16
CompositeIterator: 19
Composite'Fast'Iterator: 12
CompositeVeryVeryFastIterator: 10