Skip to content

Commit 7a65911

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 aa8f0fd commit 7a65911

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
@@ -1247,9 +1247,39 @@ impl Write for &File {
12471247
}
12481248
#[stable(feature = "rust1", since = "1.0.0")]
12491249
impl Seek for &File {
1250+
/// Seek to an offset, in bytes in a file.
1251+
///
1252+
/// See [`Seek::seek`] docs for more info.
1253+
///
1254+
/// # Platform-specific behavior
1255+
///
1256+
/// This function currently corresponds to the `lseek64` function on Unix
1257+
/// and the `SetFilePointerEx` function on Windows. Note that this [may
1258+
/// change in the future][changes].
1259+
///
1260+
/// [changes]: io#platform-specific-behavior
12501261
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
12511262
self.inner.seek(pos)
12521263
}
1264+
1265+
/// Returns the length of this file (in bytes).
1266+
///
1267+
/// See [`Seek::stream_len`] docs for more info.
1268+
///
1269+
/// # Platform-specific behavior
1270+
///
1271+
/// This function currently corresponds to the `statx` function on Linux
1272+
/// (with fallbacks) and the `GetFileSizeEx` function on Windows. Note that
1273+
/// this [may change in the future][changes].
1274+
///
1275+
/// [changes]: io#platform-specific-behavior
1276+
fn stream_len(&mut self) -> io::Result<u64> {
1277+
if let Some(result) = self.inner.size() {
1278+
return result;
1279+
}
1280+
io::stream_len_default(self)
1281+
}
1282+
12531283
fn stream_position(&mut self) -> io::Result<u64> {
12541284
self.inner.tell()
12551285
}
@@ -1299,6 +1329,9 @@ impl Seek for File {
12991329
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
13001330
(&*self).seek(pos)
13011331
}
1332+
fn stream_len(&mut self) -> io::Result<u64> {
1333+
(&*self).stream_len()
1334+
}
13021335
fn stream_position(&mut self) -> io::Result<u64> {
13031336
(&*self).stream_position()
13041337
}
@@ -1348,6 +1381,9 @@ impl Seek for Arc<File> {
13481381
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
13491382
(&**self).seek(pos)
13501383
}
1384+
fn stream_len(&mut self) -> io::Result<u64> {
1385+
(&**self).stream_len()
1386+
}
13511387
fn stream_position(&mut self) -> io::Result<u64> {
13521388
(&**self).stream_position()
13531389
}

library/std/src/io/mod.rs

+14-10
Original file line numberDiff line numberDiff line change
@@ -2062,16 +2062,7 @@ pub trait Seek {
20622062
/// ```
20632063
#[unstable(feature = "seek_stream_len", issue = "59359")]
20642064
fn stream_len(&mut self) -> Result<u64> {
2065-
let old_pos = self.stream_position()?;
2066-
let len = self.seek(SeekFrom::End(0))?;
2067-
2068-
// Avoid seeking a third time when we were already at the end of the
2069-
// stream. The branch is usually way cheaper than a seek operation.
2070-
if old_pos != len {
2071-
self.seek(SeekFrom::Start(old_pos))?;
2072-
}
2073-
2074-
Ok(len)
2065+
stream_len_default(self)
20752066
}
20762067

20772068
/// Returns the current seek position from the start of the stream.
@@ -2132,6 +2123,19 @@ pub trait Seek {
21322123
}
21332124
}
21342125

2126+
pub(crate) fn stream_len_default<T: Seek + ?Sized>(self_: &mut T) -> Result<u64> {
2127+
let old_pos = self_.stream_position()?;
2128+
let len = self_.seek(SeekFrom::End(0))?;
2129+
2130+
// Avoid seeking a third time when we were already at the end of the
2131+
// stream. The branch is usually way cheaper than a seek operation.
2132+
if old_pos != len {
2133+
self_.seek(SeekFrom::Start(old_pos))?;
2134+
}
2135+
2136+
Ok(len)
2137+
}
2138+
21352139
/// Enumeration of possible methods to seek within an I/O object.
21362140
///
21372141
/// 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
@@ -1450,6 +1450,15 @@ impl File {
14501450
Ok(n as u64)
14511451
}
14521452

1453+
pub fn size(&self) -> Option<io::Result<u64>> {
1454+
match self.file_attr().map(|attr| attr.size()) {
1455+
// Fall back to default implementation if the returned size is 0,
1456+
// we might be in a proc mount.
1457+
Ok(0) => None,
1458+
result => Some(result),
1459+
}
1460+
}
1461+
14531462
pub fn tell(&self) -> io::Result<u64> {
14541463
self.seek(SeekFrom::Current(0))
14551464
}

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)