Skip to content

Commit af59081

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 6be7b0c commit af59081

File tree

10 files changed

+85
-10
lines changed

10 files changed

+85
-10
lines changed

library/std/src/fs.rs

+35
Original file line numberDiff line numberDiff line change
@@ -823,9 +823,38 @@ impl Write for &File {
823823
}
824824
#[stable(feature = "rust1", since = "1.0.0")]
825825
impl Seek for &File {
826+
/// Seek to an offset, in bytes in a file.
827+
///
828+
/// See [`Seek::seek`] docs for more info.
829+
///
830+
/// # Platform-specific behavior
831+
///
832+
/// This function currently corresponds to the `lseek64` function on Unix
833+
/// and the `SetFilePointerEx` function on Windows. Note that this [may
834+
/// change in the future][changes].
835+
///
836+
/// [changes]: io#platform-specific-behavior
826837
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
827838
self.inner.seek(pos)
828839
}
840+
841+
/// Returns the length of this file (in bytes).
842+
///
843+
/// See [`Seek::stream_len`] docs for more info.
844+
///
845+
/// # Platform-specific behavior
846+
///
847+
/// This function currently corresponds to the `statx` function on Linux
848+
/// (with fallbacks) and the `GetFileSizeEx` function on Windows. Note that
849+
/// this [may change in the future][changes].
850+
///
851+
/// [changes]: io#platform-specific-behavior
852+
fn stream_len(&mut self) -> io::Result<u64> {
853+
if let Some(result) = self.inner.size() {
854+
return result;
855+
}
856+
io::stream_len_default(self)
857+
}
829858
}
830859

831860
#[stable(feature = "rust1", since = "1.0.0")]
@@ -872,6 +901,9 @@ impl Seek for File {
872901
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
873902
(&*self).seek(pos)
874903
}
904+
fn stream_len(&mut self) -> io::Result<u64> {
905+
(&*self).stream_len()
906+
}
875907
}
876908

877909
#[stable(feature = "io_traits_arc", since = "1.73.0")]
@@ -918,6 +950,9 @@ impl Seek for Arc<File> {
918950
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
919951
(&**self).seek(pos)
920952
}
953+
fn stream_len(&mut self) -> io::Result<u64> {
954+
(&*self).stream_len()
955+
}
921956
}
922957

923958
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.
@@ -2066,6 +2057,19 @@ pub trait Seek {
20662057
}
20672058
}
20682059

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

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

+4
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,10 @@ impl File {
426426
Err(Error::from_raw_os_error(22))
427427
}
428428

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

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
@@ -1307,6 +1307,15 @@ impl File {
13071307
Ok(n as u64)
13081308
}
13091309

1310+
pub fn size(&self) -> Option<io::Result<u64>> {
1311+
match self.file_attr().map(|attr| attr.size()) {
1312+
// Fall back to default implementation if the returned size is 0,
1313+
// we might be in a proc mount.
1314+
Ok(0) => None,
1315+
result => Some(result),
1316+
}
1317+
}
1318+
13101319
pub fn duplicate(&self) -> io::Result<File> {
13111320
self.0.duplicate().map(File)
13121321
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,10 @@ extern "system" {
273273
) -> BOOL;
274274
}
275275
#[link(name = "kernel32")]
276+
extern "system" {
277+
pub fn GetFileSizeEx(hfile: HANDLE, lpfilesize: *mut i64) -> BOOL;
278+
}
279+
#[link(name = "kernel32")]
276280
extern "system" {
277281
pub fn GetFileType(hfile: HANDLE) -> FILE_TYPE;
278282
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,12 @@ 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(cvt(unsafe { c::GetFileSizeEx(self.handle.as_raw_handle(), &mut result) })
507+
.map(|_| result as u64))
508+
}
509+
504510
pub fn duplicate(&self) -> io::Result<File> {
505511
Ok(Self { handle: self.handle.try_clone()? })
506512
}

0 commit comments

Comments
 (0)