path: improve path.resolve() performance when used as process.cwd()

`path.resolve()` and `path.resolve('.')` is frequently called as
alternative to process.cwd(). This minimized overhead for these
specific cases.

PR-URL: https://github.com/nodejs/node/pull/58362
Reviewed-By: Jordan Harband <ljharb@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com>
This commit is contained in:
Ruben Bridgewater 2025-05-19 15:23:57 +02:00 committed by Antoine du Hamel
parent 72a1b061f3
commit 25ee328d2b
No known key found for this signature in database
GPG Key ID: 21D900FFDB233756
8 changed files with 39 additions and 15 deletions

View File

@ -16,8 +16,8 @@ function main({ n, paths }) {
bench.start();
for (let i = 0; i < n; i++) {
if (i % 3 === 0) {
copy[1] = `${orig}${i}`;
if (i % 5 === 0) {
copy[1] = `${orig}/${i}`;
posix.join(...copy);
} else {
posix.join(...args);

View File

@ -16,8 +16,8 @@ function main({ n, paths }) {
bench.start();
for (let i = 0; i < n; i++) {
if (i % 3 === 0) {
copy[1] = `${orig}${i}`;
if (i % 5 === 0) {
copy[1] = `${orig}\\${i}`;
win32.join(...copy);
} else {
win32.join(...args);

View File

@ -17,7 +17,7 @@ const bench = common.createBenchmark(main, {
function main({ n, path }) {
bench.start();
for (let i = 0; i < n; i++) {
posix.normalize(i % 3 === 0 ? `${path}${i}` : path);
posix.normalize(i % 5 === 0 ? `${path}/${i}` : path);
}
bench.end(n);
}

View File

@ -17,7 +17,7 @@ const bench = common.createBenchmark(main, {
function main({ n, path }) {
bench.start();
for (let i = 0; i < n; i++) {
win32.normalize(i % 3 === 0 ? `${path}${i}` : path);
win32.normalize(i % 5 === 0 ? `${path}\\${i}` : path);
}
bench.end(n);
}

View File

@ -4,23 +4,26 @@ const { posix } = require('path');
const bench = common.createBenchmark(main, {
paths: [
'empty',
'',
'.',
['', ''].join('|'),
['foo/bar', '/tmp/file/', '..', 'a/../subfile'].join('|'),
['a/b/c/', '../../..'].join('|'),
['/a/b/c/', 'abc'].join('|'),
],
n: [1e5],
});
function main({ n, paths }) {
const args = paths.split('|');
const args = paths === 'empty' ? [] : paths.split('|');
const copy = [...args];
const orig = copy[0];
const orig = copy[0] ?? '';
bench.start();
for (let i = 0; i < n; i++) {
if (i % 3 === 0) {
copy[0] = `${orig}${i}`;
if (i % 5 === 0) {
copy[0] = `${orig}/${i}`;
posix.resolve(...copy);
} else {
posix.resolve(...args);

View File

@ -4,7 +4,9 @@ const { win32 } = require('path');
const bench = common.createBenchmark(main, {
paths: [
'empty',
'',
'.',
['', ''].join('|'),
['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'].join('|'),
['c:/blah\\blah', 'd:/games', 'c:../a'].join('|'),
@ -13,14 +15,14 @@ const bench = common.createBenchmark(main, {
});
function main({ n, paths }) {
const args = paths.split('|');
const args = paths === 'empty' ? [] : paths.split('|');
const copy = [...args];
const orig = copy[0];
const orig = copy[0] ?? '';
bench.start();
for (let i = 0; i < n; i++) {
if (i % 3 === 0) {
copy[0] = `${orig}${i}`;
if (i % 5 === 0) {
copy[0] = `${orig}\\${i}`;
win32.resolve(...copy);
} else {
win32.resolve(...args);

View File

@ -96,7 +96,7 @@ function normalizeString(path, allowAboveRoot, separator, isPathSeparator) {
StringPrototypeCharCodeAt(res, res.length - 1) !== CHAR_DOT ||
StringPrototypeCharCodeAt(res, res.length - 2) !== CHAR_DOT) {
if (res.length > 2) {
const lastSlashIndex = StringPrototypeLastIndexOf(res, separator);
const lastSlashIndex = res.length - lastSegmentLength - 1;
if (lastSlashIndex === -1) {
res = '';
lastSegmentLength = 0;
@ -178,6 +178,7 @@ function glob(path, pattern, windows) {
nocaseMagicOnly: true,
});
}
const forwardSlashRegExp = /\//g;
const win32 = {
/**
@ -202,6 +203,14 @@ const win32 = {
}
} else if (resolvedDevice.length === 0) {
path = process.cwd();
// Fast path for current directory
if (args.length === 0 || ((args.length === 1 && (args[0] === '' || args[0] === '.')) &&
isPathSeparator(StringPrototypeCharCodeAt(path, 0)))) {
if (!isWindows) {
path = StringPrototypeReplace(path, forwardSlashRegExp, '\\');
}
return path;
}
} else {
// Windows has the concept of drive-specific current working
// directories. If we've resolved a drive letter but not yet an
@ -1176,6 +1185,12 @@ const posix = {
* @returns {string}
*/
resolve(...args) {
if (args.length === 0 || (args.length === 1 && (args[0] === '' || args[0] === '.'))) {
const cwd = posixCwd();
if (StringPrototypeCharCodeAt(cwd, 0) === CHAR_FORWARD_SLASH) {
return cwd;
}
}
let resolvedPath = '';
let resolvedAbsolute = false;

View File

@ -24,6 +24,8 @@ const resolveTests = [
[['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'], 'd:\\e.exe'],
[['c:/ignore', 'c:/some/file'], 'c:\\some\\file'],
[['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'],
[[], process.cwd()],
[[''], process.cwd()],
[['.'], process.cwd()],
[['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative'],
[['c:/', '//'], 'c:\\'],
@ -40,6 +42,8 @@ const resolveTests = [
[[['/var/lib', '../', 'file/'], '/var/file'],
[['/var/lib', '/../', 'file/'], '/file'],
[['a/b/c/', '../../..'], posixyCwd],
[[], posixyCwd],
[[''], posixyCwd],
[['.'], posixyCwd],
[['/some/dir', '.', '/absolute/'], '/absolute'],
[['/foo/tmp.3/', '../tmp.3/cycles/root.js'], '/foo/tmp.3/cycles/root.js'],