Skip to content

Commit 50e5f96

Browse files
authored
perf(utils): add benchmarks for file system walk and glob libs (#514)
1 parent b8c620d commit 50e5f96

File tree

9 files changed

+723
-131
lines changed

9 files changed

+723
-131
lines changed

package-lock.json

+583-121
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"@commitlint/config-nx-scopes": "^18.4.3",
6868
"@commitlint/cz-commitlint": "^17.7.1",
6969
"@jscutlery/semver": "^4.1.0",
70+
"@nodelib/fs.walk": "^2.0.0",
7071
"@nx/devkit": "17.1.3",
7172
"@nx/esbuild": "17.1.3",
7273
"@nx/eslint-plugin": "17.1.3",
@@ -110,6 +111,9 @@
110111
"eslint-plugin-sonarjs": "^0.22.0",
111112
"eslint-plugin-unicorn": "^48.0.1",
112113
"eslint-plugin-vitest": "^0.3.8",
114+
"fast-glob": "^3.3.2",
115+
"glob": "^10.3.10",
116+
"globby": "^14.0.1",
113117
"husky": "^8.0.0",
114118
"inquirer": "^8.2.6",
115119
"jsdom": "~22.1.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { type Entry, walkStream } from '@nodelib/fs.walk';
2+
import { CrawlFileSystemOptions } from '../../src';
3+
4+
// from https://github.com/webpro/knip/pull/426
5+
export function crawlFileSystemFsWalk<T = string>(
6+
options: CrawlFileSystemOptions<T>,
7+
): Promise<T[]> {
8+
const { directory } = options;
9+
10+
return new Promise((resolve, reject) => {
11+
const result: T[] = [];
12+
const stream = walkStream(directory);
13+
14+
stream.on('data', (entry: Entry) => {
15+
// eslint-disable-next-line functional/immutable-data
16+
result.push(entry.path as T);
17+
});
18+
19+
stream.on('error', error => {
20+
reject(error);
21+
});
22+
23+
stream.on('end', () => {
24+
resolve(result);
25+
});
26+
});
27+
}

packages/utils/perf/crawl-file-system/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
CrawlFileSystemOptions,
55
crawlFileSystem,
66
} from '../../src/lib/file-system';
7-
import { crawlFileSystemOptimized0 } from './optimized0';
7+
import { crawlFileSystemFsWalk } from './fs-walk';
88

99
const PROCESS_ARGUMENT_TARGET_DIRECTORY =
1010
process.argv
@@ -54,7 +54,7 @@ const options = {
5454
pattern: PATTERN,
5555
};
5656
suite.add('Base', wrapWithDefer(crawlFileSystem));
57-
suite.add('Optimized 0', wrapWithDefer(crawlFileSystemOptimized0));
57+
suite.add('nodelib.fsWalk', wrapWithDefer(crawlFileSystemFsWalk));
5858

5959
// ==================
6060

packages/utils/perf/crawl-file-system/optimized0.ts

-8
This file was deleted.

packages/utils/perf/glob/fast-glob.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import * as fg from 'fast-glob';
2+
3+
export function fastGlob(pattern: string[]): Promise<string[]> {
4+
return fg.async(pattern);
5+
}

packages/utils/perf/glob/glob.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { glob as g } from 'glob';
2+
3+
export function glob(pattern: string[]): Promise<string[]> {
4+
return g(pattern);
5+
}

packages/utils/perf/glob/globby.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { globby as g } from 'globby';
2+
3+
export function globby(pattern: string[]): Promise<string[]> {
4+
return g(pattern);
5+
}

packages/utils/perf/glob/index.ts

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import * as Benchmark from 'benchmark';
2+
import { join } from 'node:path';
3+
import { fastGlob } from './fast-glob';
4+
import { glob } from './glob';
5+
import { globby } from './globby';
6+
7+
const suite = new Benchmark.Suite('report-scoring');
8+
9+
const BASE_PATH = join(
10+
process.cwd(),
11+
'..',
12+
'..',
13+
'..',
14+
'node_modules',
15+
'**/*.js',
16+
);
17+
18+
// ==================
19+
20+
const start = performance.now();
21+
22+
// Add listener
23+
const listeners = {
24+
cycle: function (event: Benchmark.Event) {
25+
console.info(String(event.target));
26+
},
27+
complete: () => {
28+
if (typeof suite.filter === 'function') {
29+
console.info(' ');
30+
console.info(
31+
`Total Duration: ${((performance.now() - start) / 1000).toFixed(
32+
2,
33+
)} sec`,
34+
);
35+
console.info(`Fastest is ${String(suite.filter('fastest').map('name'))}`);
36+
}
37+
},
38+
};
39+
40+
// ==================
41+
42+
// Add tests
43+
const pattern = [BASE_PATH];
44+
suite.add('glob', wrapWithDefer(glob));
45+
suite.add('globby', wrapWithDefer(globby));
46+
suite.add('fastGlob', wrapWithDefer(fastGlob));
47+
48+
// ==================
49+
50+
// Add Listener
51+
Object.entries(listeners).forEach(([name, fn]) => {
52+
suite.on(name, fn);
53+
});
54+
55+
// ==================
56+
57+
console.info('You can adjust the test with the following arguments:');
58+
console.info(`pattern glob pattern of test --pattern=${BASE_PATH}`);
59+
console.info(' ');
60+
console.info('Start benchmark...');
61+
console.info(' ');
62+
63+
suite.run({
64+
async: true,
65+
});
66+
67+
// ==============================================================
68+
69+
function wrapWithDefer(asyncFn: (pattern: string[]) => Promise<string[]>) {
70+
const logged: Record<string, boolean> = {};
71+
return {
72+
defer: true, // important for async functions
73+
fn: function (deferred: { resolve: () => void }) {
74+
return asyncFn(pattern)
75+
.catch(() => [])
76+
.then((result: unknown[]) => {
77+
if (result.length === 0) {
78+
throw new Error(`Result length is ${result.length}`);
79+
} else {
80+
if (!logged[asyncFn.name]) {
81+
// eslint-disable-next-line functional/immutable-data
82+
logged[asyncFn.name] = true;
83+
// eslint-disable-next-line no-console
84+
console.log(`${asyncFn.name} found ${result.length} files`);
85+
}
86+
deferred.resolve();
87+
}
88+
return void 0;
89+
});
90+
},
91+
};
92+
}

0 commit comments

Comments
 (0)