1b279d4d10
Lua CI / lua-language-server type check (push) Failing after 1s
Lua CI / luacheck lint (push) Failing after 1s
Python CI / Python bindings (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Neovim aarch64-linux-android (push) Failing after 3s
Build & Publish / Build Neovim aarch64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build Neovim aarch64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build Neovim x86_64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build Neovim x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build C FFI aarch64-linux-android (push) Failing after 1s
Build & Publish / Build C FFI aarch64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build C FFI aarch64-unknown-linux-musl (push) Failing after 0s
Build & Publish / Build C FFI x86_64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build C FFI x86_64-unknown-linux-musl (push) Failing after 3s
Build & Publish / Build MCP aarch64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build MCP aarch64-unknown-linux-musl (push) Failing after 0s
Build & Publish / Build MCP x86_64-unknown-linux-gnu (push) Failing after 2s
Rust CI / Fuzz Tests (ubuntu-latest) (push) Failing after 1s
Rust CI / Build i686-unknown-linux-gnu (push) Failing after 1s
Rust CI / cargo fmt (push) Failing after 1s
Build & Publish / Build MCP x86_64-unknown-linux-musl (push) Failing after 4s
Build & Publish / Build Python wheels aarch64 (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Python wheels x86_64 (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Python sdist (push) Failing after 0s
Rust CI / Test (ubuntu-latest) (push) Failing after 1s
Rust CI / cargo clippy (push) Failing after 1s
Spelling / Spell Check with Typos (push) Failing after 1s
Stylua / Check lua files using Stylua (push) Failing after 1s
e2e Tests / e2e (ubuntu-latest) (push) Failing after 4s
e2e Tests / e2e (alpine-musl) (push) Successful in 58m54s
Build & Publish / Build C FFI aarch64-apple-darwin (push) Has been cancelled
Build & Publish / Build C FFI aarch64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build C FFI x86_64-apple-darwin (push) Has been cancelled
Build & Publish / Build C FFI x86_64-pc-windows-msvc (push) Has been cancelled
e2e Tests / e2e (macos-latest) (push) Has been cancelled
e2e Tests / e2e (windows-latest) (push) Has been cancelled
Nix CI / check (push) Has been cancelled
Python CI / Python bindings (macos-latest) (push) Has been cancelled
Python CI / Python bindings (windows-latest) (push) Has been cancelled
Build & Publish / Build Neovim aarch64-apple-darwin (push) Has been cancelled
Build & Publish / Build Neovim aarch64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build Neovim x86_64-apple-darwin (push) Has been cancelled
Build & Publish / Build Neovim x86_64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build MCP aarch64-apple-darwin (push) Has been cancelled
Build & Publish / Build MCP aarch64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build MCP x86_64-apple-darwin (push) Has been cancelled
Build & Publish / Build MCP x86_64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build Python wheels aarch64 (macos-latest) (push) Has been cancelled
Build & Publish / Build Python wheels x86_64 (macos-latest) (push) Has been cancelled
Build & Publish / Build Python wheels x86_64 (windows-latest) (push) Has been cancelled
Build & Publish / Release (push) Has been cancelled
Build & Publish / Publish Python wheels to PyPI (push) Has been cancelled
Build & Publish / Publish Rust crates (push) Has been cancelled
Build & Publish / Publish npm packages (push) Has been cancelled
Rust CI / Test (macos-latest) (push) Has been cancelled
Rust CI / Test (windows-latest) (push) Has been cancelled
Rust CI / Fuzz Tests (macos-latest) (push) Has been cancelled
Rust CI / Fuzz Tests (windows-latest) (push) Has been cancelled
154 lines
5.2 KiB
TypeScript
154 lines
5.2 KiB
TypeScript
#!/usr/bin/env bun
|
|
/**
|
|
* Glob benchmark: fff.glob vs Bun.Glob vs npm `glob`.
|
|
*
|
|
* Each engine is asked to enumerate files in a directory matching the same
|
|
* pattern. We measure wall-clock time + result count. fff scans + indexes
|
|
* once on init; the subsequent glob call is a filter over the in-memory
|
|
* index — that's what we time.
|
|
*
|
|
* Usage:
|
|
* bun examples/glob-bench.ts [dir] [pattern] [iterations]
|
|
*
|
|
* dir default: cwd
|
|
* pattern default: "**\/*.ts"
|
|
* iterations default: 5 (each engine runs N times, best+median reported)
|
|
*
|
|
* Install npm glob first:
|
|
* bun add glob
|
|
*/
|
|
|
|
import { performance } from "node:perf_hooks";
|
|
import { resolve } from "node:path";
|
|
import { Glob as BunGlob } from "bun";
|
|
import { FileFinder } from "../src/index";
|
|
|
|
// npm glob — optional. Skip silently if not installed.
|
|
let npmGlob: ((pattern: string, opts: { cwd: string }) => Promise<string[]>) | null =
|
|
null;
|
|
try {
|
|
const mod: {
|
|
glob: (pattern: string, opts: { cwd: string }) => Promise<string[]>;
|
|
} =
|
|
// @ts-ignore - optional peer; resolved at runtime, may be absent
|
|
await import("glob");
|
|
npmGlob = mod.glob;
|
|
} catch {
|
|
console.warn("npm `glob` not installed — skipping. Run: bun add glob");
|
|
}
|
|
|
|
const dir = resolve(process.argv[2] ?? process.cwd());
|
|
const pattern = process.argv[3] ?? "**/lua/**/*.lua";
|
|
const iterations = Number(process.argv[4] ?? 5);
|
|
|
|
console.log(`dir: ${dir}`);
|
|
console.log(`pattern: ${pattern}`);
|
|
console.log(`iterations: ${iterations}\n`);
|
|
|
|
interface Sample {
|
|
ms: number;
|
|
count: number;
|
|
}
|
|
|
|
function summarize(label: string, samples: Sample[]): void {
|
|
if (samples.length === 0) {
|
|
console.log(`${label.padEnd(16)} skipped`);
|
|
return;
|
|
}
|
|
const sorted = [...samples].sort((a, b) => a.ms - b.ms);
|
|
const best = sorted[0]!;
|
|
const median = sorted[Math.floor(sorted.length / 2)]!;
|
|
const worst = sorted[sorted.length - 1]!;
|
|
const counts = new Set(samples.map((s) => s.count));
|
|
const countStr = counts.size === 1 ? `${best.count}` : `[${[...counts].join(", ")}]`;
|
|
console.log(
|
|
`${label.padEnd(16)} best=${best.ms.toFixed(2)}ms median=${median.ms.toFixed(2)}ms worst=${worst.ms.toFixed(2)}ms count=${countStr}`,
|
|
);
|
|
}
|
|
|
|
async function bench<T>(fn: () => Promise<T> | T): Promise<{ ms: number; result: T }> {
|
|
const start = performance.now();
|
|
const result = await fn();
|
|
return { ms: performance.now() - start, result };
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// fff: init + warm scan, then time only the .glob() call. Init cost is
|
|
// reported separately because it's amortized across many subsequent calls.
|
|
// ---------------------------------------------------------------------------
|
|
const fffInit = await bench(() => {
|
|
const result = FileFinder.create({
|
|
basePath: dir,
|
|
disableMmapCache: true,
|
|
disableContentIndexing: true,
|
|
disableWatch: true,
|
|
});
|
|
if (!result.ok) throw new Error(result.error);
|
|
return result.value;
|
|
});
|
|
const finder = fffInit.result;
|
|
|
|
// Wait until initial scan done so the first .glob() doesn't see a partial
|
|
// index. Returns true = completed, false = timed out.
|
|
const scanReady = finder.waitForScanBlocking(30_000);
|
|
if (!scanReady.ok || !scanReady.value) {
|
|
console.error("fff: initial scan did not finish in 30s — exiting");
|
|
process.exit(1);
|
|
}
|
|
console.log(`fff init+scan: ${fffInit.ms.toFixed(2)}ms\n`);
|
|
|
|
const fffSamples: Sample[] = [];
|
|
for (let i = 0; i < iterations; i++) {
|
|
const r = await bench(() => {
|
|
const out = finder.glob(pattern, { pageSize: 100 });
|
|
if (!out.ok) throw new Error(out.error);
|
|
return out.value;
|
|
});
|
|
fffSamples.push({ ms: r.ms, count: r.result.items.length });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Bun.Glob — sync iterator, returns relative paths.
|
|
// ---------------------------------------------------------------------------
|
|
const bunSamples: Sample[] = [];
|
|
for (let i = 0; i < iterations; i++) {
|
|
const r = await bench(() => {
|
|
const g = new BunGlob(pattern);
|
|
let count = 0;
|
|
for (const _ of g.scanSync({ cwd: dir })) count++;
|
|
return count;
|
|
});
|
|
bunSamples.push({ ms: r.ms, count: r.result });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// npm glob — async, returns absolute or relative paths depending on opts.
|
|
// ---------------------------------------------------------------------------
|
|
const npmSamples: Sample[] = [];
|
|
if (npmGlob) {
|
|
for (let i = 0; i < iterations; i++) {
|
|
const r = await bench(() => npmGlob!(pattern, { cwd: dir }));
|
|
npmSamples.push({ ms: r.ms, count: r.result.length });
|
|
}
|
|
}
|
|
|
|
console.log("results:");
|
|
summarize("fff.glob", fffSamples);
|
|
summarize("Bun.Glob", bunSamples);
|
|
summarize("npm glob", npmSamples);
|
|
|
|
// Sanity: counts should be in the same ballpark. They won't match exactly
|
|
// because indexing rules differ (fff respects gitignore + skips binaries by
|
|
// default; Bun.Glob and npm glob do not).
|
|
const counts = {
|
|
fff: fffSamples[0]?.count ?? 0,
|
|
bun: bunSamples[0]?.count ?? 0,
|
|
npm: npmSamples[0]?.count ?? 0,
|
|
};
|
|
console.log(
|
|
`\nNote: fff respects gitignore + skips binaries; Bun.Glob and npm glob walk the raw filesystem. Count differences are expected.`,
|
|
);
|
|
console.log(`raw counts: fff=${counts.fff} bun=${counts.bun} npm=${counts.npm}`);
|
|
|
|
finder.destroy();
|