Skip to content

Commit 9ade806

Browse files
committed
perf: use GetFileAttributesExW for symlink metadata lookup on Windows (#691)
[`std::fs::symlink_metadata`](https://doc.rust-lang.org/beta/std/fs/fn.symlink_metadata.html) implementation uses `GetFileInformationByHandle` on Windows, which seems to be known as slow [^1]. This PR adds a function that uses `GetFileAttributesExW` instead of it, which is faster than `GetFileInformationByHandle`. ## Benchmarks I tested this PR with [rolldown/benchmarks](https://github.com/rolldown/benchmarks). It improved the build time by ~10% I took the following results with `hyperfine 'node --run build:rolldown'` ### rome **Before** ``` Benchmark 1: node --run build:rolldown Time (mean ± σ): 237.7 ms ± 3.7 ms [User: 513.1 ms, System: 495.2 ms] Range (min … max): 233.1 ms … 243.5 ms 12 runs ``` **After** ``` Benchmark 1: node --run build:rolldown Time (mean ± σ): 210.4 ms ± 5.3 ms [User: 422.1 ms, System: 443.5 ms] Range (min … max): 204.1 ms … 223.1 ms 13 runs ``` **Result**: -27.3ms (-11.5%) ### three10x **Before** ``` Benchmark 1: node --run build:rolldown Time (mean ± σ): 367.7 ms ± 5.1 ms [User: 1313.1 ms, System: 1283.1 ms] Range (min … max): 359.7 ms … 374.5 ms 10 runs ``` **After** ``` Benchmark 1: node --run build:rolldown Time (mean ± σ): 334.3 ms ± 7.5 ms [User: 1094.7 ms, System: 1019.1 ms] Range (min … max): 324.1 ms … 346.6 ms 10 runs ``` **Result**: -33.4ms (-9.1%) ### apps/10000 **Before** ``` Benchmark 1: node --run build:rolldown Time (mean ± σ): 2.459 s ± 0.042 s [User: 3.858 s, System: 5.627 s] Range (min … max): 2.401 s … 2.509 s 10 runs ``` **After** ``` Benchmark 1: node --run build:rolldown Time (mean ± σ): 1.744 s ± 0.017 s [User: 3.300 s, System: 3.796 s] Range (min … max): 1.720 s … 1.771 s 10 runs ``` **Result**: -0.72s (-29.1%) [^1]: gradle/native-platform#203, dotnet/msbuild#2052.
1 parent 9bfd35a commit 9ade806

File tree

6 files changed

+350
-2
lines changed

6 files changed

+350
-2
lines changed

Cargo.lock

Lines changed: 106 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ pnp = { version = "0.12.2", optional = true }
9494

9595
document-features = { version = "0.2.11", optional = true }
9696

97+
[target.'cfg(target_os = "windows")'.dependencies]
98+
windows = { version = "0.62.0", features = ["Win32_Storage_FileSystem"] }
99+
97100
[dev-dependencies]
98101
criterion2 = { version = "3.0.2", default-features = false }
99102
dirs = { version = "6.0.0" }

deny.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ allow = [
9595
"Apache-2.0",
9696
"Unicode-3.0",
9797
"BSD-2-Clause",
98+
"MPL-2.0",
9899
# "Apache-2.0 WITH LLVM-exception",
99100
]
100101
# The confidence threshold for detecting a license from license text.

src/file_system.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ impl FileMetadata {
9494
}
9595
}
9696

97+
#[cfg(target_os = "windows")]
98+
impl From<crate::windows::SymlinkMetadata> for FileMetadata {
99+
fn from(value: crate::windows::SymlinkMetadata) -> Self {
100+
Self::new(value.is_file, value.is_dir, value.is_symlink)
101+
}
102+
}
103+
97104
#[cfg(feature = "yarn_pnp")]
98105
impl From<pnp::fs::FileType> for FileMetadata {
99106
fn from(value: pnp::fs::FileType) -> Self {
@@ -139,15 +146,33 @@ impl FileSystemOs {
139146
/// See [std::fs::metadata]
140147
#[inline]
141148
pub fn metadata(path: &Path) -> io::Result<FileMetadata> {
142-
fs::metadata(path).map(FileMetadata::from)
149+
#[cfg(target_os = "windows")]
150+
{
151+
let result = crate::windows::symlink_metadata(path)?;
152+
if result.is_symlink {
153+
return fs::metadata(path).map(FileMetadata::from);
154+
}
155+
Ok(result.into())
156+
}
157+
#[cfg(not(target_os = "windows"))]
158+
{
159+
fs::metadata(path).map(FileMetadata::from)
160+
}
143161
}
144162

145163
/// # Errors
146164
///
147165
/// See [std::fs::symlink_metadata]
148166
#[inline]
149167
pub fn symlink_metadata(path: &Path) -> io::Result<FileMetadata> {
150-
fs::symlink_metadata(path).map(FileMetadata::from)
168+
#[cfg(target_os = "windows")]
169+
{
170+
Ok(crate::windows::symlink_metadata(path)?.into())
171+
}
172+
#[cfg(not(target_os = "windows"))]
173+
{
174+
fs::symlink_metadata(path).map(FileMetadata::from)
175+
}
151176
}
152177

153178
/// # Errors

0 commit comments

Comments
 (0)