Skip to content
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

runtime: the scavenger doesn't call MADV_NOHUGEPAGE like it used to #55328

Open
mknyszek opened this issue Sep 21, 2022 · 1 comment
Open

runtime: the scavenger doesn't call MADV_NOHUGEPAGE like it used to #55328

mknyszek opened this issue Sep 21, 2022 · 1 comment
Assignees
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-Linux Performance
Milestone

Comments

@mknyszek
Copy link
Contributor

mknyszek commented Sep 21, 2022

Because the Go runtime's background scavenger releases memory in 64 KiB chunks, the heuristic in mem_linux.go's implementation of sysUnusedOS never actually calls MADV_NOHUGEPAGE to break up huge pages that would otherwise keep a lot of memory around. The reason for this is that there's a heuristic preventing the call on every sysUnusedOS to avoid creating too many VMAs (Linux has a low limit).

The result of all this is that Linux may be keeping around huge pages transparently using up a lot more memory than intended, and reducing the effectiveness of the scavenger.

I think the fix for this is to mark all new heap memory as MADV_NOHUGEPAGE initially, and then only call it again in circumstances where we free memory that is likely to have had MADV_HUGEPAGE called on it (e.g. a large object that contains at least one aligned 2 MiB region will have this happen via sysUsed). However, this might not be enough, and needs some more investigation.

CC @golang/runtime

@mknyszek mknyszek added Performance OS-Linux NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Sep 21, 2022
@mknyszek mknyszek added this to the Go1.20 milestone Sep 21, 2022
@mknyszek mknyszek self-assigned this Sep 21, 2022
@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Sep 21, 2022
@mknyszek
Copy link
Contributor Author

mknyszek commented Sep 22, 2022

Thinking about this more, what my proposed solution loses is the benefit of backing dense parts of the heap with huge pages.

However, back when we worked on the scavenger, there was a key insight about having a GC'd heap: as we reach the end of a GC cycle, the whole heap is always densely packed. I wonder if we should say something like "back up the heap with MADV_HUGEPAGE up to the heap goal." Due to fragmentation this leaves a non-huge-page tail, but that's OK.

At first I thought the goroutine doing mark termination could spend a little extra time post-STW to go over the (mostly-contiguous) heap and call MADV_HUGEPAGE and MADV_NOHUGEPAGE a bunch of times, but maybe what really should be happening is just the MADV_HUGEPAGE part, and the scavenger then takes care of MADV_NOHUGEPAGE as it walks down the heap from the highest addresses. It means there's a delay, but there's a chance for the scavenger to take advantage of calling MADV_DONTNEED on hugepages which is considerably faster. This may not be worth it, however. It's not hard to try both approaches.

This is in effect a very simple "huge page aware" allocation policy without needing to do any special bin-packing. We're taking advantage of the fact that we can count on heap density, thanks to the GC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-Linux Performance
Projects
Status: In Progress
Development

No branches or pull requests

2 participants