Skip to content

Commit 5a76ab7

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 65d7296 commit 5a76ab7

File tree

10 files changed

+84
-10
lines changed

10 files changed

+84
-10
lines changed

library/std/src/fs.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,9 +1203,38 @@ impl Write for &File {
12031203
}
12041204
#[stable(feature = "rust1", since = "1.0.0")]
12051205
impl Seek for &File {
1206+
/// Seek to an offset, in bytes in a file.
1207+
///
1208+
/// See [`Seek::seek`] docs for more info.
1209+
///
1210+
/// # Platform-specific behavior
1211+
///
1212+
/// This function currently corresponds to the `lseek64` function on Unix
1213+
/// and the `SetFilePointerEx` function on Windows. Note that this [may
1214+
/// change in the future][changes].
1215+
///
1216+
/// [changes]: io#platform-specific-behavior
12061217
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
12071218
self.inner.seek(pos)
12081219
}
1220+
1221+
/// Returns the length of this file (in bytes).
1222+
///
1223+
/// See [`Seek::stream_len`] docs for more info.
1224+
///
1225+
/// # Platform-specific behavior
1226+
///
1227+
/// This function currently corresponds to the `statx` function on Linux
1228+
/// (with fallbacks) and the `GetFileSizeEx` function on Windows. Note that
1229+
/// this [may change in the future][changes].
1230+
///
1231+
/// [changes]: io#platform-specific-behavior
1232+
fn stream_len(&mut self) -> io::Result<u64> {
1233+
if let Some(result) = self.inner.size() {
1234+
return result;
1235+
}
1236+
io::stream_len_default(self)
1237+
}
12091238
}
12101239

12111240
#[stable(feature = "rust1", since = "1.0.0")]
@@ -1252,6 +1281,9 @@ impl Seek for File {
12521281
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
12531282
(&*self).seek(pos)
12541283
}
1284+
fn stream_len(&mut self) -> io::Result<u64> {
1285+
(&*self).stream_len()
1286+
}
12551287
}
12561288

12571289
#[stable(feature = "io_traits_arc", since = "1.73.0")]
@@ -1298,6 +1330,9 @@ impl Seek for Arc<File> {
12981330
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
12991331
(&**self).seek(pos)
13001332
}
1333+
fn stream_len(&mut self) -> io::Result<u64> {
1334+
(&**self).stream_len()
1335+
}
13011336
}
13021337

13031338
impl OpenOptions {

library/std/src/io/mod.rs

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

20632054
/// Returns the current seek position from the start of the stream.
@@ -2118,6 +2109,19 @@ pub trait Seek {
21182109
}
21192110
}
21202111

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

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,10 @@ impl File {
425425
Err(Error::from_raw_os_error(22))
426426
}
427427

428+
pub fn size(&self) -> Option<io::Result<u64>> {
429+
None
430+
}
431+
428432
pub fn duplicate(&self) -> io::Result<File> {
429433
Err(Error::from_raw_os_error(22))
430434
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,10 @@ impl File {
470470
}
471471
}
472472

473+
pub fn size(&self) -> Option<io::Result<u64>> {
474+
None
475+
}
476+
473477
pub fn duplicate(&self) -> io::Result<File> {
474478
unsupported()
475479
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,6 +1434,15 @@ impl File {
14341434
Ok(n as u64)
14351435
}
14361436

1437+
pub fn size(&self) -> Option<io::Result<u64>> {
1438+
match self.file_attr().map(|attr| attr.size()) {
1439+
// Fall back to default implementation if the returned size is 0,
1440+
// we might be in a proc mount.
1441+
Ok(0) => None,
1442+
result => Some(result),
1443+
}
1444+
}
1445+
14371446
pub fn duplicate(&self) -> io::Result<File> {
14381447
self.0.duplicate().map(File)
14391448
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ impl File {
258258
self.0
259259
}
260260

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

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,10 @@ impl File {
517517
self.fd.seek(pos)
518518
}
519519

520+
pub fn size(&self) -> Option<io::Result<u64>> {
521+
None
522+
}
523+
520524
pub fn duplicate(&self) -> io::Result<File> {
521525
// https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup
522526
unsupported()

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2346,6 +2346,7 @@ Windows.Win32.Storage.FileSystem.FlushFileBuffers
23462346
Windows.Win32.Storage.FileSystem.GetFileAttributesW
23472347
Windows.Win32.Storage.FileSystem.GetFileInformationByHandle
23482348
Windows.Win32.Storage.FileSystem.GetFileInformationByHandleEx
2349+
Windows.Win32.Storage.FileSystem.GetFileSizeEx
23492350
Windows.Win32.Storage.FileSystem.GetFileType
23502351
Windows.Win32.Storage.FileSystem.GETFINALPATHNAMEBYHANDLE_FLAGS
23512352
Windows.Win32.Storage.FileSystem.GetFinalPathNameByHandleW

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ windows_targets::link!("kernel32.dll" "system" fn GetExitCodeProcess(hprocess :
4545
windows_targets::link!("kernel32.dll" "system" fn GetFileAttributesW(lpfilename : PCWSTR) -> u32);
4646
windows_targets::link!("kernel32.dll" "system" fn GetFileInformationByHandle(hfile : HANDLE, lpfileinformation : *mut BY_HANDLE_FILE_INFORMATION) -> BOOL);
4747
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);
48+
windows_targets::link!("kernel32.dll" "system" fn GetFileSizeEx(hfile : HANDLE, lpfilesize : *mut i64) -> BOOL);
4849
windows_targets::link!("kernel32.dll" "system" fn GetFileType(hfile : HANDLE) -> FILE_TYPE);
4950
windows_targets::link!("kernel32.dll" "system" fn GetFinalPathNameByHandleW(hfile : HANDLE, lpszfilepath : PWSTR, cchfilepath : u32, dwflags : GETFINALPATHNAMEBYHANDLE_FLAGS) -> u32);
5051
windows_targets::link!("kernel32.dll" "system" fn GetFullPathNameW(lpfilename : PCWSTR, nbufferlength : u32, lpbuffer : PWSTR, lpfilepart : *mut PWSTR) -> u32);

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,14 @@ impl File {
630630
Ok(newpos as u64)
631631
}
632632

633+
pub fn size(&self) -> Option<io::Result<u64>> {
634+
let mut result = 0;
635+
Some(
636+
cvt(unsafe { c::GetFileSizeEx(self.handle.as_raw_handle(), &mut result) })
637+
.map(|_| result as u64),
638+
)
639+
}
640+
633641
pub fn duplicate(&self) -> io::Result<File> {
634642
Ok(Self { handle: self.handle.try_clone()? })
635643
}

0 commit comments

Comments
 (0)