Skip to content

Commit c006cca

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 d819876 commit c006cca

File tree

10 files changed

+84
-10
lines changed

10 files changed

+84
-10
lines changed

library/std/src/fs.rs

+35
Original file line numberDiff line numberDiff line change
@@ -899,9 +899,38 @@ impl Write for &File {
899899
}
900900
#[stable(feature = "rust1", since = "1.0.0")]
901901
impl Seek for &File {
902+
/// Seek to an offset, in bytes in a file.
903+
///
904+
/// See [`Seek::seek`] docs for more info.
905+
///
906+
/// # Platform-specific behavior
907+
///
908+
/// This function currently corresponds to the `lseek64` function on Unix
909+
/// and the `SetFilePointerEx` function on Windows. Note that this [may
910+
/// change in the future][changes].
911+
///
912+
/// [changes]: io#platform-specific-behavior
902913
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
903914
self.inner.seek(pos)
904915
}
916+
917+
/// Returns the length of this file (in bytes).
918+
///
919+
/// See [`Seek::stream_len`] docs for more info.
920+
///
921+
/// # Platform-specific behavior
922+
///
923+
/// This function currently corresponds to the `statx` function on Linux
924+
/// (with fallbacks) and the `GetFileSizeEx` function on Windows. Note that
925+
/// this [may change in the future][changes].
926+
///
927+
/// [changes]: io#platform-specific-behavior
928+
fn stream_len(&mut self) -> io::Result<u64> {
929+
if let Some(result) = self.inner.size() {
930+
return result;
931+
}
932+
io::stream_len_default(self)
933+
}
905934
}
906935

907936
#[stable(feature = "rust1", since = "1.0.0")]
@@ -948,6 +977,9 @@ impl Seek for File {
948977
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
949978
(&*self).seek(pos)
950979
}
980+
fn stream_len(&mut self) -> io::Result<u64> {
981+
(&*self).stream_len()
982+
}
951983
}
952984

953985
#[stable(feature = "io_traits_arc", since = "1.73.0")]
@@ -994,6 +1026,9 @@ impl Seek for Arc<File> {
9941026
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
9951027
(&**self).seek(pos)
9961028
}
1029+
fn stream_len(&mut self) -> io::Result<u64> {
1030+
(&**self).stream_len()
1031+
}
9971032
}
9981033

9991034
impl OpenOptions {

library/std/src/io/mod.rs

+14-10
Original file line numberDiff line numberDiff line change
@@ -1995,16 +1995,7 @@ pub trait Seek {
19951995
/// ```
19961996
#[unstable(feature = "seek_stream_len", issue = "59359")]
19971997
fn stream_len(&mut self) -> Result<u64> {
1998-
let old_pos = self.stream_position()?;
1999-
let len = self.seek(SeekFrom::End(0))?;
2000-
2001-
// Avoid seeking a third time when we were already at the end of the
2002-
// stream. The branch is usually way cheaper than a seek operation.
2003-
if old_pos != len {
2004-
self.seek(SeekFrom::Start(old_pos))?;
2005-
}
2006-
2007-
Ok(len)
1998+
stream_len_default(self)
20081999
}
20092000

20102001
/// Returns the current seek position from the start of the stream.
@@ -2065,6 +2056,19 @@ pub trait Seek {
20652056
}
20662057
}
20672058

2059+
pub(crate) fn stream_len_default<T: Seek + ?Sized>(self_: &mut T) -> Result<u64> {
2060+
let old_pos = self_.stream_position()?;
2061+
let len = self_.seek(SeekFrom::End(0))?;
2062+
2063+
// Avoid seeking a third time when we were already at the end of the
2064+
// stream. The branch is usually way cheaper than a seek operation.
2065+
if old_pos != len {
2066+
self_.seek(SeekFrom::Start(old_pos))?;
2067+
}
2068+
2069+
Ok(len)
2070+
}
2071+
20682072
/// Enumeration of possible methods to seek within an I/O object.
20692073
///
20702074
/// It is used by the [`Seek`] trait.

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

+4
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,10 @@ impl File {
411411
Err(Error::from_raw_os_error(22))
412412
}
413413

414+
pub fn size(&self) -> Option<io::Result<u64>> {
415+
None
416+
}
417+
414418
pub fn duplicate(&self) -> io::Result<File> {
415419
Err(Error::from_raw_os_error(22))
416420
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,10 @@ impl File {
455455
}
456456
}
457457

458+
pub fn size(&self) -> Option<io::Result<u64>> {
459+
None
460+
}
461+
458462
pub fn duplicate(&self) -> io::Result<File> {
459463
unsupported()
460464
}

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

+9
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,15 @@ impl File {
13031303
Ok(n as u64)
13041304
}
13051305

1306+
pub fn size(&self) -> Option<io::Result<u64>> {
1307+
match self.file_attr().map(|attr| attr.size()) {
1308+
// Fall back to default implementation if the returned size is 0,
1309+
// we might be in a proc mount.
1310+
Ok(0) => None,
1311+
result => Some(result),
1312+
}
1313+
}
1314+
13061315
pub fn duplicate(&self) -> io::Result<File> {
13071316
self.0.duplicate().map(File)
13081317
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,10 @@ impl File {
238238
self.0
239239
}
240240

241+
pub fn size(&self) -> io::Result<u64> {
242+
self.0
243+
}
244+
241245
pub fn duplicate(&self) -> io::Result<File> {
242246
self.0
243247
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,10 @@ impl File {
465465
self.fd.seek(pos)
466466
}
467467

468+
pub fn size(&self) -> Option<io::Result<u64>> {
469+
None
470+
}
471+
468472
pub fn duplicate(&self) -> io::Result<File> {
469473
// https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup
470474
unsupported()

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

+1
Original file line numberDiff line numberDiff line change
@@ -2336,6 +2336,7 @@ Windows.Win32.Storage.FileSystem.FlushFileBuffers
23362336
Windows.Win32.Storage.FileSystem.GetFileAttributesW
23372337
Windows.Win32.Storage.FileSystem.GetFileInformationByHandle
23382338
Windows.Win32.Storage.FileSystem.GetFileInformationByHandleEx
2339+
Windows.Win32.Storage.FileSystem.GetFileSizeEx
23392340
Windows.Win32.Storage.FileSystem.GetFileType
23402341
Windows.Win32.Storage.FileSystem.GETFINALPATHNAMEBYHANDLE_FLAGS
23412342
Windows.Win32.Storage.FileSystem.GetFinalPathNameByHandleW

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

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ windows_targets::link!("kernel32.dll" "system" fn GetExitCodeProcess(hprocess :
4242
windows_targets::link!("kernel32.dll" "system" fn GetFileAttributesW(lpfilename : PCWSTR) -> u32);
4343
windows_targets::link!("kernel32.dll" "system" fn GetFileInformationByHandle(hfile : HANDLE, lpfileinformation : *mut BY_HANDLE_FILE_INFORMATION) -> BOOL);
4444
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);
45+
windows_targets::link!("kernel32.dll" "system" fn GetFileSizeEx(hfile : HANDLE, lpfilesize : *mut i64) -> BOOL);
4546
windows_targets::link!("kernel32.dll" "system" fn GetFileType(hfile : HANDLE) -> FILE_TYPE);
4647
windows_targets::link!("kernel32.dll" "system" fn GetFinalPathNameByHandleW(hfile : HANDLE, lpszfilepath : PWSTR, cchfilepath : u32, dwflags : GETFINALPATHNAMEBYHANDLE_FLAGS) -> u32);
4748
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

+8
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,14 @@ impl File {
501501
Ok(newpos as u64)
502502
}
503503

504+
pub fn size(&self) -> Option<io::Result<u64>> {
505+
let mut result = 0;
506+
Some(
507+
cvt(unsafe { c::GetFileSizeEx(self.handle.as_raw_handle(), &mut result) })
508+
.map(|_| result as u64),
509+
)
510+
}
511+
504512
pub fn duplicate(&self) -> io::Result<File> {
505513
Ok(Self { handle: self.handle.try_clone()? })
506514
}

0 commit comments

Comments
 (0)