Skip to content

Commit 4e8ef65

Browse files
committed
Optimize Seek::stream_len impl for File
It uses the file metadata on Unix with a fallback for files incorrectly reported as zero-sized. It uses `GetFileSizeEx` on Windows. This reduces the number of syscalls needed for determining the file size of an open file from 3 to 1.
1 parent f7b4354 commit 4e8ef65

File tree

10 files changed

+85
-10
lines changed

10 files changed

+85
-10
lines changed

library/std/src/fs.rs

+36
Original file line numberDiff line numberDiff line change
@@ -1242,9 +1242,39 @@ impl Write for &File {
12421242
}
12431243
#[stable(feature = "rust1", since = "1.0.0")]
12441244
impl Seek for &File {
1245+
/// Seek to an offset, in bytes in a file.
1246+
///
1247+
/// See [`Seek::seek`] docs for more info.
1248+
///
1249+
/// # Platform-specific behavior
1250+
///
1251+
/// This function currently corresponds to the `lseek64` function on Unix
1252+
/// and the `SetFilePointerEx` function on Windows. Note that this [may
1253+
/// change in the future][changes].
1254+
///
1255+
/// [changes]: io#platform-specific-behavior
12451256
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
12461257
self.inner.seek(pos)
12471258
}
1259+
1260+
/// Returns the length of this file (in bytes).
1261+
///
1262+
/// See [`Seek::stream_len`] docs for more info.
1263+
///
1264+
/// # Platform-specific behavior
1265+
///
1266+
/// This function currently corresponds to the `statx` function on Linux
1267+
/// (with fallbacks) and the `GetFileSizeEx` function on Windows. Note that
1268+
/// this [may change in the future][changes].
1269+
///
1270+
/// [changes]: io#platform-specific-behavior
1271+
fn stream_len(&mut self) -> io::Result<u64> {
1272+
if let Some(result) = self.inner.size() {
1273+
return result;
1274+
}
1275+
io::stream_len_default(self)
1276+
}
1277+
12481278
fn stream_position(&mut self) -> io::Result<u64> {
12491279
self.inner.tell()
12501280
}
@@ -1294,6 +1324,9 @@ impl Seek for File {
12941324
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
12951325
(&*self).seek(pos)
12961326
}
1327+
fn stream_len(&mut self) -> io::Result<u64> {
1328+
(&*self).stream_len()
1329+
}
12971330
fn stream_position(&mut self) -> io::Result<u64> {
12981331
(&*self).stream_position()
12991332
}
@@ -1343,6 +1376,9 @@ impl Seek for Arc<File> {
13431376
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
13441377
(&**self).seek(pos)
13451378
}
1379+
fn stream_len(&mut self) -> io::Result<u64> {
1380+
(&**self).stream_len()
1381+
}
13461382
}
13471383

13481384
impl OpenOptions {

library/std/src/io/mod.rs

+14-10
Original file line numberDiff line numberDiff line change
@@ -2051,16 +2051,7 @@ pub trait Seek {
20512051
/// ```
20522052
#[unstable(feature = "seek_stream_len", issue = "59359")]
20532053
fn stream_len(&mut self) -> Result<u64> {
2054-
let old_pos = self.stream_position()?;
2055-
let len = self.seek(SeekFrom::End(0))?;
2056-
2057-
// Avoid seeking a third time when we were already at the end of the
2058-
// stream. The branch is usually way cheaper than a seek operation.
2059-
if old_pos != len {
2060-
self.seek(SeekFrom::Start(old_pos))?;
2061-
}
2062-
2063-
Ok(len)
2054+
stream_len_default(self)
20642055
}
20652056

20662057
/// Returns the current seek position from the start of the stream.
@@ -2121,6 +2112,19 @@ pub trait Seek {
21212112
}
21222113
}
21232114

2115+
pub(crate) fn stream_len_default<T: Seek + ?Sized>(self_: &mut T) -> Result<u64> {
2116+
let old_pos = self_.stream_position()?;
2117+
let len = self_.seek(SeekFrom::End(0))?;
2118+
2119+
// Avoid seeking a third time when we were already at the end of the
2120+
// stream. The branch is usually way cheaper than a seek operation.
2121+
if old_pos != len {
2122+
self_.seek(SeekFrom::Start(old_pos))?;
2123+
}
2124+
2125+
Ok(len)
2126+
}
2127+
21242128
/// Enumeration of possible methods to seek within an I/O object.
21252129
///
21262130
/// It is used by the [`Seek`] trait.

library/std/src/sys/fs/hermit.rs

+4
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,10 @@ impl File {
421421
self.0.seek(pos)
422422
}
423423

424+
pub fn size(&self) -> Option<io::Result<u64>> {
425+
None
426+
}
427+
424428
pub fn tell(&self) -> io::Result<u64> {
425429
self.0.tell()
426430
}

library/std/src/sys/fs/solid.rs

+4
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,10 @@ impl File {
458458
self.tell()
459459
}
460460

461+
pub fn size(&self) -> Option<io::Result<u64>> {
462+
None
463+
}
464+
461465
pub fn tell(&self) -> io::Result<u64> {
462466
unsafe {
463467
let mut out_offset = MaybeUninit::uninit();

library/std/src/sys/fs/unix.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1441,6 +1441,15 @@ impl File {
14411441
Ok(n as u64)
14421442
}
14431443

1444+
pub fn size(&self) -> Option<io::Result<u64>> {
1445+
match self.file_attr().map(|attr| attr.size()) {
1446+
// Fall back to default implementation if the returned size is 0,
1447+
// we might be in a proc mount.
1448+
Ok(0) => None,
1449+
result => Some(result),
1450+
}
1451+
}
1452+
14441453
pub fn tell(&self) -> io::Result<u64> {
14451454
self.seek(SeekFrom::Current(0))
14461455
}

library/std/src/sys/fs/unsupported.rs

+4
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ impl File {
258258
self.0
259259
}
260260

261+
pub fn size(&self) -> Option<io::Result<u64>> {
262+
self.0
263+
}
264+
261265
pub fn tell(&self) -> io::Result<u64> {
262266
self.0
263267
}

library/std/src/sys/fs/wasi.rs

+4
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,10 @@ impl File {
515515
self.fd.seek(pos)
516516
}
517517

518+
pub fn size(&self) -> Option<io::Result<u64>> {
519+
None
520+
}
521+
518522
pub fn tell(&self) -> io::Result<u64> {
519523
self.fd.tell()
520524
}

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

+8
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,14 @@ impl File {
619619
Ok(newpos as u64)
620620
}
621621

622+
pub fn size(&self) -> Option<io::Result<u64>> {
623+
let mut result = 0;
624+
Some(
625+
cvt(unsafe { c::GetFileSizeEx(self.handle.as_raw_handle(), &mut result) })
626+
.map(|_| result as u64),
627+
)
628+
}
629+
622630
pub fn tell(&self) -> io::Result<u64> {
623631
self.seek(SeekFrom::Current(0))
624632
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -2156,6 +2156,7 @@ GetExitCodeProcess
21562156
GetFileAttributesW
21572157
GetFileInformationByHandle
21582158
GetFileInformationByHandleEx
2159+
GetFileSizeEx
21592160
GetFileType
21602161
GETFINALPATHNAMEBYHANDLE_FLAGS
21612162
GetFinalPathNameByHandleW

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

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ windows_targets::link!("kernel32.dll" "system" fn GetExitCodeProcess(hprocess :
4444
windows_targets::link!("kernel32.dll" "system" fn GetFileAttributesW(lpfilename : PCWSTR) -> u32);
4545
windows_targets::link!("kernel32.dll" "system" fn GetFileInformationByHandle(hfile : HANDLE, lpfileinformation : *mut BY_HANDLE_FILE_INFORMATION) -> BOOL);
4646
windows_targets::link!("kernel32.dll" "system" fn GetFileInformationByHandleEx(hfile : HANDLE, fileinformationclass : FILE_INFO_BY_HANDLE_CLASS, lpfileinformation : *mut core::ffi::c_void, dwbuffersize : u32) -> BOOL);
47+
windows_targets::link!("kernel32.dll" "system" fn GetFileSizeEx(hfile : HANDLE, lpfilesize : *mut i64) -> BOOL);
4748
windows_targets::link!("kernel32.dll" "system" fn GetFileType(hfile : HANDLE) -> FILE_TYPE);
4849
windows_targets::link!("kernel32.dll" "system" fn GetFinalPathNameByHandleW(hfile : HANDLE, lpszfilepath : PWSTR, cchfilepath : u32, dwflags : GETFINALPATHNAMEBYHANDLE_FLAGS) -> u32);
4950
windows_targets::link!("kernel32.dll" "system" fn GetFullPathNameW(lpfilename : PCWSTR, nbufferlength : u32, lpbuffer : PWSTR, lpfilepart : *mut PWSTR) -> u32);

0 commit comments

Comments
 (0)