Skip to content

Commit 3ec4308

Browse files
committed
Auto merge of rust-lang#131972 - klensy:FindFirstFileExW, r=ChrisDenton
speedup directory traversal on windows Optimizes walking over dirs on windows by replacing `FindFirstFileW` with `FindFirstFileExW` with `FindExInfoBasic` option, that allows skipping filling unused struct field which should be faster. Also adds the same change for fallback call of `FindFirstFileExW` in metadata call. Locally shows small speedup, but bench results from other users are welcome.
2 parents 3e33bda + 2920ed0 commit 3ec4308

File tree

4 files changed

+41
-14
lines changed

4 files changed

+41
-14
lines changed

library/std/src/fs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2618,7 +2618,7 @@ pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
26182618
/// # Platform-specific behavior
26192619
///
26202620
/// This function currently corresponds to the `opendir` function on Unix
2621-
/// and the `FindFirstFile` function on Windows. Advancing the iterator
2621+
/// and the `FindFirstFileEx` function on Windows. Advancing the iterator
26222622
/// currently corresponds to `readdir` on Unix and `FindNextFile` on Windows.
26232623
/// Note that, this [may change in the future][changes].
26242624
///

library/std/src/sys/pal/windows/c/bindings.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -2337,7 +2337,9 @@ Windows.Win32.Storage.FileSystem.FileStandardInfo
23372337
Windows.Win32.Storage.FileSystem.FileStorageInfo
23382338
Windows.Win32.Storage.FileSystem.FileStreamInfo
23392339
Windows.Win32.Storage.FileSystem.FindClose
2340-
Windows.Win32.Storage.FileSystem.FindFirstFileW
2340+
Windows.Win32.Storage.FileSystem.FindExInfoBasic
2341+
Windows.Win32.Storage.FileSystem.FindExSearchNameMatch
2342+
Windows.Win32.Storage.FileSystem.FindFirstFileExW
23412343
Windows.Win32.Storage.FileSystem.FindNextFileW
23422344
Windows.Win32.Storage.FileSystem.FlushFileBuffers
23432345
Windows.Win32.Storage.FileSystem.GetFileAttributesW

library/std/src/sys/pal/windows/c/windows_sys.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ windows_targets::link!("kernel32.dll" "system" fn DeviceIoControl(hdevice : HAND
2626
windows_targets::link!("kernel32.dll" "system" fn DuplicateHandle(hsourceprocesshandle : HANDLE, hsourcehandle : HANDLE, htargetprocesshandle : HANDLE, lptargethandle : *mut HANDLE, dwdesiredaccess : u32, binherithandle : BOOL, dwoptions : DUPLICATE_HANDLE_OPTIONS) -> BOOL);
2727
windows_targets::link!("kernel32.dll" "system" fn ExitProcess(uexitcode : u32) -> !);
2828
windows_targets::link!("kernel32.dll" "system" fn FindClose(hfindfile : HANDLE) -> BOOL);
29-
windows_targets::link!("kernel32.dll" "system" fn FindFirstFileW(lpfilename : PCWSTR, lpfindfiledata : *mut WIN32_FIND_DATAW) -> HANDLE);
29+
windows_targets::link!("kernel32.dll" "system" fn FindFirstFileExW(lpfilename : PCWSTR, finfolevelid : FINDEX_INFO_LEVELS, lpfindfiledata : *mut core::ffi::c_void, fsearchop : FINDEX_SEARCH_OPS, lpsearchfilter : *const core::ffi::c_void, dwadditionalflags : FIND_FIRST_EX_FLAGS) -> HANDLE);
3030
windows_targets::link!("kernel32.dll" "system" fn FindNextFileW(hfindfile : HANDLE, lpfindfiledata : *mut WIN32_FIND_DATAW) -> BOOL);
3131
windows_targets::link!("kernel32.dll" "system" fn FlushFileBuffers(hfile : HANDLE) -> BOOL);
3232
windows_targets::link!("kernel32.dll" "system" fn FormatMessageW(dwflags : FORMAT_MESSAGE_OPTIONS, lpsource : *const core::ffi::c_void, dwmessageid : u32, dwlanguageid : u32, lpbuffer : PWSTR, nsize : u32, arguments : *const *const i8) -> u32);
@@ -2501,6 +2501,9 @@ pub const FILE_WRITE_ATTRIBUTES: FILE_ACCESS_RIGHTS = 256u32;
25012501
pub const FILE_WRITE_DATA: FILE_ACCESS_RIGHTS = 2u32;
25022502
pub const FILE_WRITE_EA: FILE_ACCESS_RIGHTS = 16u32;
25032503
pub const FILE_WRITE_THROUGH: NTCREATEFILE_CREATE_OPTIONS = 2u32;
2504+
pub type FINDEX_INFO_LEVELS = i32;
2505+
pub type FINDEX_SEARCH_OPS = i32;
2506+
pub type FIND_FIRST_EX_FLAGS = u32;
25042507
pub const FIONBIO: i32 = -2147195266i32;
25052508
#[repr(C)]
25062509
#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec", target_arch = "x86_64"))]
@@ -2565,6 +2568,8 @@ pub const FileRenameInfoEx: FILE_INFO_BY_HANDLE_CLASS = 22i32;
25652568
pub const FileStandardInfo: FILE_INFO_BY_HANDLE_CLASS = 1i32;
25662569
pub const FileStorageInfo: FILE_INFO_BY_HANDLE_CLASS = 16i32;
25672570
pub const FileStreamInfo: FILE_INFO_BY_HANDLE_CLASS = 7i32;
2571+
pub const FindExInfoBasic: FINDEX_INFO_LEVELS = 1i32;
2572+
pub const FindExSearchNameMatch: FINDEX_SEARCH_OPS = 0i32;
25682573
pub type GENERIC_ACCESS_RIGHTS = u32;
25692574
pub const GENERIC_ALL: GENERIC_ACCESS_RIGHTS = 268435456u32;
25702575
pub const GENERIC_EXECUTE: GENERIC_ACCESS_RIGHTS = 536870912u32;
@@ -3307,7 +3312,6 @@ pub struct XSAVE_FORMAT {
33073312
pub XmmRegisters: [M128A; 8],
33083313
pub Reserved4: [u8; 224],
33093314
}
3310-
33113315
#[cfg(target_arch = "arm")]
33123316
#[repr(C)]
33133317
pub struct WSADATA {

library/std/src/sys/pal/windows/fs.rs

+31-10
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ impl Iterator for ReadDir {
114114
fn next(&mut self) -> Option<io::Result<DirEntry>> {
115115
if self.handle.0 == c::INVALID_HANDLE_VALUE {
116116
// This iterator was initialized with an `INVALID_HANDLE_VALUE` as its handle.
117-
// Simply return `None` because this is only the case when `FindFirstFileW` in
117+
// Simply return `None` because this is only the case when `FindFirstFileExW` in
118118
// the construction of this iterator returns `ERROR_FILE_NOT_FOUND` which means
119119
// no matchhing files can be found.
120120
return None;
@@ -1047,8 +1047,22 @@ pub fn readdir(p: &Path) -> io::Result<ReadDir> {
10471047
let path = maybe_verbatim(&star)?;
10481048

10491049
unsafe {
1050-
let mut wfd = mem::zeroed();
1051-
let find_handle = c::FindFirstFileW(path.as_ptr(), &mut wfd);
1050+
let mut wfd: c::WIN32_FIND_DATAW = mem::zeroed();
1051+
// this is like FindFirstFileW (see https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfileexw),
1052+
// but with FindExInfoBasic it should skip filling WIN32_FIND_DATAW.cAlternateFileName
1053+
// (see https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw)
1054+
// (which will be always null string value and currently unused) and should be faster.
1055+
//
1056+
// We can pass FIND_FIRST_EX_LARGE_FETCH to dwAdditionalFlags to speed up things more,
1057+
// but as we don't know user's use profile of this function, lets be conservative.
1058+
let find_handle = c::FindFirstFileExW(
1059+
path.as_ptr(),
1060+
c::FindExInfoBasic,
1061+
&mut wfd as *mut _ as _,
1062+
c::FindExSearchNameMatch,
1063+
ptr::null(),
1064+
0,
1065+
);
10521066

10531067
if find_handle != c::INVALID_HANDLE_VALUE {
10541068
Ok(ReadDir {
@@ -1057,7 +1071,7 @@ pub fn readdir(p: &Path) -> io::Result<ReadDir> {
10571071
first: Some(wfd),
10581072
})
10591073
} else {
1060-
// The status `ERROR_FILE_NOT_FOUND` is returned by the `FindFirstFileW` function
1074+
// The status `ERROR_FILE_NOT_FOUND` is returned by the `FindFirstFileExW` function
10611075
// if no matching files can be found, but not necessarily that the path to find the
10621076
// files in does not exist.
10631077
//
@@ -1079,7 +1093,7 @@ pub fn readdir(p: &Path) -> io::Result<ReadDir> {
10791093

10801094
// Just return the error constructed from the raw OS error if the above is not the case.
10811095
//
1082-
// Note: `ERROR_PATH_NOT_FOUND` would have been returned by the `FindFirstFileW` function
1096+
// Note: `ERROR_PATH_NOT_FOUND` would have been returned by the `FindFirstFileExW` function
10831097
// when the path to search in does not exist in the first place.
10841098
Err(Error::from_raw_os_error(last_error.code as i32))
10851099
}
@@ -1220,7 +1234,7 @@ fn metadata(path: &Path, reparse: ReparsePoint) -> io::Result<FileAttr> {
12201234
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | reparse.as_flag());
12211235

12221236
// Attempt to open the file normally.
1223-
// If that fails with `ERROR_SHARING_VIOLATION` then retry using `FindFirstFileW`.
1237+
// If that fails with `ERROR_SHARING_VIOLATION` then retry using `FindFirstFileExW`.
12241238
// If the fallback fails for any reason we return the original error.
12251239
match File::open(path, &opts) {
12261240
Ok(file) => file.file_attr(),
@@ -1237,13 +1251,20 @@ fn metadata(path: &Path, reparse: ReparsePoint) -> io::Result<FileAttr> {
12371251
unsafe {
12381252
let path = maybe_verbatim(path)?;
12391253

1240-
// `FindFirstFileW` accepts wildcard file names.
1254+
// `FindFirstFileExW` accepts wildcard file names.
12411255
// Fortunately wildcards are not valid file names and
12421256
// `ERROR_SHARING_VIOLATION` means the file exists (but is locked)
12431257
// therefore it's safe to assume the file name given does not
12441258
// include wildcards.
1245-
let mut wfd = mem::zeroed();
1246-
let handle = c::FindFirstFileW(path.as_ptr(), &mut wfd);
1259+
let mut wfd: c::WIN32_FIND_DATAW = mem::zeroed();
1260+
let handle = c::FindFirstFileExW(
1261+
path.as_ptr(),
1262+
c::FindExInfoBasic,
1263+
&mut wfd as *mut _ as _,
1264+
c::FindExSearchNameMatch,
1265+
ptr::null(),
1266+
0,
1267+
);
12471268

12481269
if handle == c::INVALID_HANDLE_VALUE {
12491270
// This can fail if the user does not have read access to the
@@ -1253,7 +1274,7 @@ fn metadata(path: &Path, reparse: ReparsePoint) -> io::Result<FileAttr> {
12531274
// We no longer need the find handle.
12541275
c::FindClose(handle);
12551276

1256-
// `FindFirstFileW` reads the cached file information from the
1277+
// `FindFirstFileExW` reads the cached file information from the
12571278
// directory. The downside is that this metadata may be outdated.
12581279
let attrs = FileAttr::from(wfd);
12591280
if reparse == ReparsePoint::Follow && attrs.file_type().is_symlink() {

0 commit comments

Comments
 (0)