-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Memory load includes the file cache on Docker Linux #49058
Comments
Tagging subscribers to this area: @dotnet/gc Issue DetailsDescriptionWe have a complex C# application that makes heavy use of memory mapped files. In Docker it spends most its time hitting the container memory limit, but this is only due to page cache so shouldn’t be a problem. Our app isn’t failing due to lack of memory. However it’s running at half the speed on Linux compared to Windows. G2 garbage collections are also way more frequent on Linux. We suspected this was due to the process garbage collecting too aggressively, as it wrongly thought it was about to run out of memory (although it’s harmlessly hitting the limit due to page cache). We conducted an experiment to test this theory. This looks similar to #13371 but for the cgroup code path. I tried working round the issue by setting System.GC.HighMemoryPercent to 100 – this makes no difference, probably because HighMemoryPercent is capped at 99 in the code (search for ConfigurationThis effect has been observed using both the .NET Core 3.1.3 and 5.0.0 runtimes.
Regression?This has always been the case AFAIK DataTypical example: (Container memory limit is 36GiB, machine has 768GiB in total) AnalysisPossibly issue in https://github.com/dotnet/runtime/blob/main/src/coreclr/gc/unix/cgroup.cpp ?
|
Hey @robertdavidsmith, is there a standalone repro you could share for this issue? |
Not currently, the app is closed-source and relies on our internal infrastructure. If the problem isn't obvious I can try and create one. It looks like https://github.com/dotnet/runtime/blob/main/src/coreclr/gc/unix/cgroup.cpp is including file cache in memory load though. |
Ok sure, we will take a look. |
Hi @mangod9, I'm working on this issue with @robertdavidsmith, I've prepared a standalone repro for it. I'm using Docker on windows, v3.2.2, Hyper-V backend with 4GB Memory and 1 GB Swap limit. I've also tested this with Docker on Linux with no swap and my results were very similar.
|
If you want to reproduce the crazy "performance doubles when the machine is low on memory" result you can do the following. This was tested on a Linux machine with 8GB RAM and swap off - memory settings will need to be different if you have a different amount of RAM. Open four console windows and type the following Window 1 (GC tester, memorypressurerate set very high so limiting factor is dotnet performance not this setting):
Window 2 (Hammer the filesystem inside the same container):
Window 3 (log stats very 5s):
Window 4 (waste memory):
The gc tester in Window 1 logs out various stats every second, including BlockAllocationsPerformed. BlockAllocationsPerformed is the amount of allocations performed in the last second. When memorypressurerate is large, BlockAllocationsPerformed is effectively a measure of performance. When the memory waster isn't running, BlockAllocationsPerformed is around 3000 blocks per second Putting the machine under memory pressure more than doubles performance! Looking at window 3, you can see memory.usage_in_bytes for the container. When wastemem isn't running, memory.usage_in_bytes equals the container memory limit (although a lot of this is only file cache, see total_inactive_file in window 3). When wastemem starts, the OS reclaims memory from the file cache, so the container is no longer at the memory limit. This more than doubles performance as the GC collects less agressively when it's not at the container limit. Also note G2 collections per second roughly halves. |
From the evidence provided above, it seems to me that we should really exclude the caches from the reported used memory. |
@janvorli maybe we can explore using the events that come from cgroup related to memory usage instead of calculating usage ourselves? I can have a look into this. I'd start by looking at the cgroupv2 |
I tried out some things, but it didn't work as I hoped. The
If we exclude the cache from usage, probably it makes sense to exclude it also from the max memory when we calculate load: For cgroup v2 we may be able to calculate something from @robertdavidsmith you may be able to work around the issue using |
Regarding cgroup v1 (I haven’t looked at v2): I think a good starting point would be use I'm not sure whether total_active_file should be subtracted as well - it might be safer to just subtract If you don't want to read a 3rd "file", note the existing value of used (which comes from "file" That Note bug report #50414 - this suggests the GC is too relaxed sometimes. This bug may get worse if we relax the GC further. |
I can't say whether this only includes reclaimable pages, or whether unevictable pages may also be counted here. |
@janvorli @robertdavidsmith @tmds I've opened a PR with my proposition of excluding page cache from memory load. According to my knowledge, on Windows the page cache is not considered as used memory but modified/dirty memory is. I wanted to avoid reading both |
Description
We have a complex C# application that makes heavy use of memory mapped files.
In Docker it spends most its time hitting the container memory limit, but this is only due to page cache so shouldn’t be a problem. Our app isn’t failing due to lack of memory. However it’s running at half the speed on Linux compared to Windows. G2 garbage collections are also way more frequent on Linux.
We suspected this was due to the process garbage collecting too aggressively, as it wrongly thought it was about to run out of memory (although it’s harmlessly hitting the limit due to page cache). We conducted an experiment to test this theory.
We tried allocating/writing to a large chunk of memory outside the container (swap is off so this stays in RAM). This stops the C# app from hitting the container memory limit because page cache size is now constrained by physical RAM on the machine, not by the cgroup limit. Allocating the large chunk of memory outside the container doubles the speed of our C# app, and reduces G2 collections by 8x!
This looks similar to #13371 but for the cgroup code path.
I tried working round the issue by setting System.GC.HighMemoryPercent to 100 – this makes no difference, probably because HighMemoryPercent is capped at 99 in the code (search for
min (99, highmem_th_from_config)
in https://raw.githubusercontent.com/dotnet/runtime/main/src/coreclr/gc/gc.cpp)Configuration
This effect has been observed using both the .NET Core 3.1.3 and 5.0.0 runtimes.
Regression?
This has always been the case AFAIK
Data
Typical example:
~700GiB Memory free at start: 10421 seconds, GC counts: G0=111932, G1=29224, G2=812
27GiB Memory free at start: 5592 seconds, GC Counts: G0=110433, G1=29013, G2=98
(Container memory limit is 36GiB, machine has 768GiB in total)
Analysis
Possibly issue in https://github.com/dotnet/runtime/blob/main/src/coreclr/gc/unix/cgroup.cpp ?
The text was updated successfully, but these errors were encountered: