-
Notifications
You must be signed in to change notification settings - Fork 13.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
High memory usage on random file reads using seek_read
(Windows)
#122473
Comments
Rust cannot control how the Windows kernel uses and manages its file cache, other than by completely opting out of caching. Which wouldn't be a desirable default. |
I agree, but it was recommended to post it here anyway 🤷 : https://twitter.com/ClintRutkas/status/1768052358955827405 Disabling buffering completely with FILE_FLAG_NO_BUFFERING helps, but it has its own issues and is awkward to work with too. |
I heard from a colleague about a memory leak involving the Rust Standard Library and suggested an issue be opened with a minimal repro. This does not however appear to be a memory leak, as you've already discovered. If you need more control over the behavior of file reads you may want to experiment with using the Windows API directly. I will just say that Windows may buffer or cache file I/O intentionally, often involving memory mapped files, to improve performance based on available memory. |
But I clearly provided a hint to OS that I do random reads in a huge file. There isn't anything OS can possibly cache to improve performance by definition of random reads, hence bug report. Linux and macOS do not have this issue and Windows should disable contents caching when such hint is provided as well. |
If you think there's a bug in the caching behavior, please provide a minimal repro using just the Windows API and ideally in C. I don't want to make extra work for you but the OS engineers who can investigate generally don't have much experience with Rust and that should make it easier for them to quickly pinpoint an issue or provide some guidance. I'll also ping some Windows/Rust folks directly in case they have some other suggestions: |
Is there an actual problem here? From the kernel's perspective, free memory is wasted memory. So seeing your free memory go to 0 is only a problem if the kernel then fails to discard cache to serve memory allocation requests. Does it? |
From the linked conversations it sounds like the memory isn't properly freed under pressure.
This kind of degradation is important to include in the issue since it's different from reclaimable memory not being reclaimed instantly. |
Hm, I missed that. If memory isn't being reclaimed when needed then that's more serious than I initially realised. Still, if it's happening in the kernel then I'm not sure Rust can do much about it. |
Yes, but:
I'll rewrite and share demo with |
Without more info, this sounds like the normal, expected behavior of the Windows kernel, specifically the buffer manager. The buffer manager caches filesystem data, regardless of what means is used to access the filesystem data. In other words, it will cache data in the kernel whether you're reading using Are you seeing any actual degradation in performance, or are you basing this problem report on counters in Task Manager? If it's just the counter, then this is generally not a problem, because using memory for caching is the expected behavior of the Windows kernel. That memory will be reclaimed, on demand.
The reason that this is the expected behavior is that the kernel is trying to provide "general" good service for apps. Random reads from files is expected, normal service, especially for executable files (which are memory-mapped, with pages being touched on-demand). All mainstream operating systems cache pieces of files. If caching is harmful for your application, then you should disable cached reads when opening the file. The documentation for CreateFileW describes the file flags that can be used when opening the file. The Random reads from very large files, with very little data locality, are exactly the reason that the |
|
@sivadeilra I'm not sure you noticed Regardless whether it is expected for Windows to cache things when No other operating systems I deal with (Linux and macOS) cache contents of files when provided random access hints because it makes no sense, so I expect very similar behavior from Windows. Working with |
Imo this is the crucial aspect for prioritization. You should provide more details about that rather than discussing whether/how much cache should or shouldn't be used. |
Access patterns and cacheability are independent aspects of an app's behavior, which is why Regardless, this is clearly not a Rust-specific issue. |
I'm closing this issue on that basis. I have no objection to people continuing to discuss this here but there's nothing for Rust to track. |
I disagree strongly and Linux/macOS developers apparently too, but I'm willing to help however I can to resolve whatever you believe the root issue is here.
Agree, where should the discussion continue? I posted this in 7 places already and this is the only place that attracted attention of relevant (I think) people. I can do email or whatever else you folks prefer. |
I have no idea why I am being involved in this conversation, I don't agree with you at all. |
@kennykerr, assuming I didn't mess up too much, here is the same app with //! ```cargo
//! [dependencies]
//! rand = "0.8.5"
//! windows = { version = "0.54.0", features = ["Wdk_Storage_FileSystem", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_Security", "Win32_System_Threading"] }
//! ```
use rand::prelude::*;
use std::env;
use std::fs::File;
use std::io::{Seek, SeekFrom};
use windows::core::PCWSTR;
use windows::Wdk::Storage::FileSystem::NtReadFile;
use windows::Win32::Foundation::{GENERIC_ACCESS_RIGHTS, GENERIC_READ, HANDLE, STATUS_PENDING};
use windows::Win32::Storage::FileSystem::FILE_FLAG_RANDOM_ACCESS;
use windows::Win32::Storage::FileSystem::{
CreateFileW, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
};
use windows::Win32::System::Threading::{WaitForSingleObject, INFINITE};
use windows::Win32::System::IO::IO_STATUS_BLOCK;
const SECTOR_SIZE: usize = 1024 * 1024 * 1024;
const CHUNK_SIZE: usize = 32;
fn main() {
let file = env::args().nth(1).unwrap();
// Just to measure size
let sectors =
(File::open(&file).unwrap().seek(SeekFrom::End(0)).unwrap() / SECTOR_SIZE as u64) as usize;
let mut path = file.encode_utf16().collect::<Vec<_>>();
path.push(0);
let file = unsafe {
let GENERIC_ACCESS_RIGHTS(access) = GENERIC_READ;
CreateFileW(
PCWSTR::from_raw(path.as_ptr()),
access,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
None,
OPEN_EXISTING,
FILE_FLAG_RANDOM_ACCESS,
HANDLE::default(),
)
.unwrap()
};
let mut result = [0u8; CHUNK_SIZE];
let mut io_status = IO_STATUS_BLOCK::default();
for i in 0.. {
for offset in 0..sectors {
let sector_offset = offset * SECTOR_SIZE;
let offset_within_sector =
thread_rng().gen_range(0..SECTOR_SIZE / CHUNK_SIZE) * CHUNK_SIZE;
unsafe {
let byteoffset = (sector_offset + offset_within_sector) as i64;
let status = NtReadFile(
file,
HANDLE::default(),
None,
None,
&mut io_status,
result.as_mut_ptr() as *mut _,
CHUNK_SIZE as u32,
Some(&byteoffset),
None,
);
if status == STATUS_PENDING {
WaitForSingleObject(file, INFINITE);
}
}
if i > 0 && i % 1000 == 0 {
println!("{i} iterations");
}
}
}
} With large enough files this eventually results in ~100% memory usage as seeing in task manager (in real app I have this operation happening in multiple threads, so memory usage is growing quickly). |
I ran your example against a 500G file for about an hour. CPU and memory usage remained stable. I was able to continue working with no system degradation. I'm running a very recent build of Windows 11, Intel i9-12900K processor, and 64GB of RAM. |
That is expected and matches what I see as well. Memory usage grows within about a minute to some level and settles after that. The amount of memory used depends on file size, to consume all 64G you will need to have multiple TB worth of files. I have users with 16T+ of SSDs on one machine and they struggle even with 64G of memory on Windows. The insight here is that you will see that process uses something like 0.5M of memory, while actively used system memory grows by a lot more than that and stays there until application is closed. |
Here is what I see with 921G file while application is running and after it was stopped: It shows that ~7.7G of memory was used by the app (it typically seems to approach 100%, but not quite reach it). When users see such picture in Task Manager they become really worried that they need to buy more RAM. I understand it is some sort of caching, but it doesn't look right no matter how I look at it. |
If there is no actual performance concern with the Windows API, as used by Rust or otherwise, and this is instead about people not being able to interpret the graphs that the Task Manager emits, then this is a feature request to the Task Manager team and strictly out of bounds of the auspices of this venue. |
I agree, but in this case Task Manager is correctly showing what is happening in the kernel: memory is being actively used by something. And this high memory usage does cause performance concerns and even crashes in some cases as mentioned before. |
If you have crash reproduction instructions, then please provide them. |
Some history and original intent behind FILE_FLAG_RANDOM_ACCESS, it did 2 things when it comes to Cache-Manager(Cc's) behavior:
(Note: The way unmap works is, at the time of a cached read, for a given file offset, Cc peeks a few pages forward and backwards and only unmaps those adjoining pages. Now, because read pattern is random, the unmap is random as well and for a large enough file with long enough random access, we can see how it can keep dodging unmap logic just enough to consume a lot of memory. However, at some point Memory Manger (Mm) should detect the on-going/imminent memory pressure and Mm should try to evict pages from memory (system cache as well as other processes' working sets) to deal with the increased memory demand. Maybe the test app runs fast enough to beat Mm's memory-pressure sensing/reclaim speed (this part about beating Mm is purely a speculation on my part!). Just because a file is accessed in random fashion doesn't mean it can never benefit from it being cached in the system, the cache hit-rate will be random (and mostly lower than that of a sequentially access file), but hopefully never zero; and the hit-rate cost-benefit would greatly depend on the workload. In the Windows parlance, cached read without caching doesn't make much sense, that's what Non-Cached (unbuffered) read is (CACHING in this context is analogous to BUFFERING). However, I DO see your point of not wanting to change the app to do non-cached reads due to alignment requirements for non-cached/unbuffered reads. While a reasonable request to expect Linux/MacOS -like behavior from Windows, it is not a complaint we have heard enough to consider investing in changing it at this moment. There's no right or wrong behavior for this particular flag, it just so happens that, at the time, Windows chose a meaning/implementation that made most sense for their ecosystem, which is different from Linux/MacOS. And Windows has to be careful about changing its behavior under the hood, lest we break some workload that might explicitly or implicitly depend on caching for RANDOM files. Hope this provides some context on "by-design" behavior that you are experiencing. @nazar-pc : As to issues with unbuffered reads, please start a new thread and we'll investigate that as well. |
Might be, but my original expectation (before knowing more than I wanted about these things) was that I reported this same issue with different wording in https://aka.ms/AApjksl
Makes sense, but every response online I saw so far was basically "it is fine, just use unbuffered", which is quite painful to do in cross-platform apps. Also I don't think outside databases (which there aren't too many of out there) there are many apps that are so read-intensive as an app I'm working on to really show case how bad it is, especially with truly random reads on huge files. If you can consider implementing something like this, that'd make my life much easier next time I face a similar requirement.
Reported on Feedback Hub with link to inimal reproduction with Windows APIs at https://aka.ms/AApjlev, thanks! It is an edge-case, but still. |
Here is a small app:
What it does is reading small chunks of a large file at random in a loop.
It can be stored in
file.rs
and then used ascargo +nightly -Zscript file.rs D:\large-file.bin
, wherelarge-file.bin
in my case is 500G.I expected to see this happen:
Windows is saying application uses 0.5MiB of RAM:
Instead, this happened:
However, total Windows memory usage in a minute or two grows by ~1GiB (sometimes less, sometimes more) and I can't find where it goes. Amount depends on file size. This looks like either bug in Windows or something fishy happening in Rust standard library, but either way seems to be outside of my knowledge to find out.
Screenshot of task manager while application is running and after application is stopped:
I later discovered that it is used by memory mapping apparently created somewhere within Windows kernel because it doesn't belong to any apps and I definitely didn't request memory mapped file:
The example above is cross-platform and doesn't have such issues on Linux. Happens on both Windows 10 and Windows 11.
This is a reduced example of a real-world app where a single app sometimes manages tens of terabytes of large files. Appreciate any hints.
Meta
rustc --version --verbose
:Initially reported at https://internals.rust-lang.org/t/high-memory-usage-on-random-file-reads-using-seek-read-windows/20396?u=nazar-pc (with some discussion), then at https://answers.microsoft.com/en-us/windows/forum/windows_10-performance/windows-is-leaking-memory-when-reading-random/965c422c-cb71-4dbb-9d3e-49dfa7b9df76, was redirected to https://learn.microsoft.com/en-us/answers/questions/1601862/windows-is-leaking-memory-when-reading-random-chun (there is useful debugging info there), then redirected to https://techcommunity.microsoft.com/t5/microsoft-learn/windows-is-leaking-memory-when-reading-random-chunks-of-a-large/m-p/4074012 by Microsoft support and finally in response to twitter post @kennykerr suggested to report it here, so here I am 🤞
The text was updated successfully, but these errors were encountered: