Skip to content

feat: enable THP for guest memory#6003

Open
marco-marangoni wants to merge 4 commits into
firecracker-microvm:mainfrom
marco-marangoni:thp
Open

feat: enable THP for guest memory#6003
marco-marangoni wants to merge 4 commits into
firecracker-microvm:mainfrom
marco-marangoni:thp

Conversation

@marco-marangoni

@marco-marangoni marco-marangoni commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Changes

This PR adds THP for the guest memory, with a new value for the huge_pages option.

Additionally, guest memory is also now 2MiB aligned, to better facilitate the promotion to huge pages for head/tail guest memory.

Boot time improvements

Relative

Environment systemd_kernel guest_cpu_boot_time guest_boot_time systemd_total
m8i.16xlarge, AL2, 5.10 -47.6% (-72.8% to -22.3%) -57.2% (-64.9% to -43.9%) -46.7% (-68.5% to -25.5%) -29.1% (-43.6% to -19.1%)
m8i.16xlarge, AL2023, 6.1 -54.0% (-76.1% to -31.6%) -64.5% (-72.3% to -54.2%) -54.5% (-73.3% to -35.8%) -33.8% (-48.6% to -24.1%)
m8i.16xlarge, AL2023, 6.18 -54.5% (-76.5% to -31.6%) -65.4% (-73.3% to -55.3%) -50.8% (-71.4% to -34.1%) -34.0% (-49.1% to -23.4%)
m8i.metal-48xl, AL2, 5.10 -24.7% (-47.9% to -8.5%) -29.2% (-34.5% to -21.8%) -21.6% (-36.5% to -9.7%) -12.4% (-22.5% to -8.2%)
m8i.metal-48xl, AL2023, 6.1 -23.5% (-44.4% to -7.9%) -32.8% (-38.8% to -24.7%) -22.8% (-39.4% to -9.2%) -12.4% (-22.2% to -7.4%)
m8i.metal-48xl, AL2023, 6.18 -24.9% (-45.0% to -9.3%) -34.1% (-38.5% to -25.7%) -19.3% (-33.9% to -9.8%) -13.0% (-21.7% to -8.1%)

Absolute

Environment systemd_kernel guest_cpu_boot_time guest_boot_time systemd_total
m8i.16xlarge, AL2, 5.10 236ms → 131ms (88–472 → 34–297) 257ms → 108ms (137–431 → 62–184) 280ms → 155ms (133–513 → 59–325) 710ms → 501ms (462–1001 → 290–676)
m8i.16xlarge, AL2023, 6.1 279ms → 136ms (101–546 → 34–316) 321ms → 113ms (164–561 → 62–214) 334ms → 159ms (163–595 → 60–342) 800ms → 528ms (510–1088 → 294–675)
m8i.16xlarge, AL2023, 6.18 281ms → 136ms (102–552 → 34–315) 325ms → 112ms (164–572 → 59–216) 362ms → 183ms (181–625 → 73–356) 803ms → 529ms (523–1092 → 301–679)
m8i.metal-48xl, AL2, 5.10 132ms → 108ms (38–298 → 24–263) 122ms → 86ms (74–210 → 52–158) 163ms → 134ms (72–324 → 51–281) 500ms → 439ms (296–712 → 242–616)
m8i.metal-48xl, AL2023, 6.1 130ms → 108ms (37–298 → 24–272) 107ms → 72ms (67–174 → 48–118) 157ms → 128ms (66–321 → 48–289) 497ms → 436ms (299–688 → 232–592)
m8i.metal-48xl, AL2023, 6.18 130ms → 107ms (37–300 → 24–262) 110ms → 72ms (68–180 → 48–119) 188ms → 157ms (86–351 → 65–311) 497ms → 433ms (292–680 → 233–592)

Source: https://buildkite.com/firecracker/mamarang-nested-a-b/builds/119/list.

Design choices

  • require guest memory to be multiple of 2MB: this is technically not required, but I think it will reduce surprises by end-users when some VM configuration have some pages that cannot be merged
  • not enabled by default: THP has some impact on the host, and feedback from internal customers was that they want to enable THP under their terms. We can re-evaluate changing the default in future Firecracker versions
  • requested optimistically: there's no guarantee that huge-pages will actually be used by the host:
  • guest memory is always 2MiB aligned, regardless of whether THP is enabled or not. This is to avoid unnecessary modalities, and enable the performance benefit when THP is enforced by the host (e.g. /sys/kernel/mm/transparent_hugepage/enabled = always).

Reason

Reduce page fault frequency, reduce TLB usage.

License Acceptance

By submitting this pull request, I confirm that my contribution is made under
the terms of the Apache 2.0 license. For more information on following Developer
Certificate of Origin and signing off your commits, please check
CONTRIBUTING.md.

PR Checklist

  • I have read and understand CONTRIBUTING.md.
  • I have run tools/devtool checkbuild --all to verify that the PR passes
    build checks on all supported architectures.
  • I have run tools/devtool checkstyle to verify that the PR passes the
    automated style checks.
  • I have described what is done in these changes, why they are needed, and
    how they are solving the problem in a clear and encompassing way.
  • I have updated any relevant documentation (both in code and in the docs)
    in the PR.
  • I have mentioned all user-facing changes in CHANGELOG.md.
  • If a specific issue led to this PR, this PR closes the issue.
  • When making API changes, I have followed the
    Runbook for Firecracker API changes.
  • I have tested all new and changed functionalities in unit tests and/or
    integration tests.
  • I have linked an issue to every new TODO.

  • This functionality cannot be added in rust-vmm.

@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.27273% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.14%. Comparing base (a0d635b) to head (cd70d79).
⚠️ Report is 46 commits behind head on main.

Files with missing lines Patch % Lines
src/vmm/src/vstate/memory.rs 86.80% 19 Missing ⚠️
src/vmm/src/vmm_config/machine_config.rs 77.77% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6003      +/-   ##
==========================================
+ Coverage   83.12%   83.14%   +0.01%     
==========================================
  Files         277      277              
  Lines       30259    30401     +142     
==========================================
+ Hits        25154    25277     +123     
- Misses       5105     5124      +19     
Flag Coverage Δ
5.10-m5n.metal 83.44% <87.27%> (+0.01%) ⬆️
5.10-m6a.metal 82.80% <87.27%> (+0.02%) ⬆️
5.10-m6g.metal 80.16% <87.27%> (+0.04%) ⬆️
5.10-m6i.metal 83.45% <87.27%> (+0.02%) ⬆️
5.10-m7a.metal-48xl 82.79% <87.27%> (+0.02%) ⬆️
5.10-m7g.metal 80.16% <87.27%> (+0.04%) ⬆️
5.10-m7i.metal-24xl 83.42% <87.27%> (+0.02%) ⬆️
5.10-m7i.metal-48xl 83.42% <87.27%> (+0.01%) ⬆️
5.10-m8g.metal-24xl 80.15% <87.27%> (+0.04%) ⬆️
5.10-m8g.metal-48xl 80.16% <87.27%> (+0.05%) ⬆️
5.10-m8i.metal-48xl 83.42% <87.27%> (+0.01%) ⬆️
5.10-m8i.metal-96xl 83.42% <87.27%> (+0.02%) ⬆️
6.1-m5n.metal 83.47% <87.27%> (+0.01%) ⬆️
6.1-m6a.metal 82.83% <87.27%> (+0.02%) ⬆️
6.1-m6g.metal 80.15% <87.27%> (+0.04%) ⬆️
6.1-m6i.metal 83.47% <87.27%> (+0.01%) ⬆️
6.1-m7a.metal-48xl 82.82% <87.27%> (+0.02%) ⬆️
6.1-m7g.metal 80.16% <87.27%> (+0.04%) ⬆️
6.1-m7i.metal-24xl 83.48% <87.27%> (+0.01%) ⬆️
6.1-m7i.metal-48xl 83.48% <87.27%> (+0.01%) ⬆️
6.1-m8g.metal-24xl 80.15% <87.27%> (+0.04%) ⬆️
6.1-m8g.metal-48xl 80.16% <87.27%> (+0.05%) ⬆️
6.1-m8i.metal-48xl 83.48% <87.27%> (+0.01%) ⬆️
6.1-m8i.metal-96xl 83.49% <87.27%> (+0.01%) ⬆️
6.18-m5n.metal 83.47% <87.27%> (+0.01%) ⬆️
6.18-m6a.metal 82.83% <87.27%> (+0.02%) ⬆️
6.18-m6g.metal 80.16% <87.27%> (+0.04%) ⬆️
6.18-m6i.metal 83.47% <87.27%> (+0.01%) ⬆️
6.18-m7a.metal-48xl 82.82% <87.27%> (+0.01%) ⬆️
6.18-m7g.metal 80.15% <87.27%> (+0.04%) ⬆️
6.18-m7i.metal-24xl 83.48% <87.27%> (+0.01%) ⬆️
6.18-m7i.metal-48xl 83.48% <87.27%> (+0.01%) ⬆️
6.18-m8g.metal-24xl 80.15% <87.27%> (+0.04%) ⬆️
6.18-m8g.metal-48xl 80.15% <87.27%> (+0.04%) ⬆️
6.18-m8i.metal-48xl 83.48% <87.27%> (+0.01%) ⬆️
6.18-m8i.metal-96xl 83.48% <87.27%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread docs/hugepages.md Outdated
Comment thread src/vmm/src/vmm_config/machine_config.rs Outdated
// Any integer memory size expressed in MiB will be a multiple of 4096KiB.
HugePageConfig::None => 1,
// Note: THP technically supports memory not 2MB aligned, however that would mean
// some pages at the tail would be forced to be 4k size. To avoid performance/fragmentation surprises,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is everybody "aligned" (no pun intended) with this? I'm not necessarily against this, but equally I don't think it's a big problem having a few 4K pages at the end of the memory region (especially since THP is not a guarantee anyway). And we already know that internal customers often use non 2MiB multiples.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we already know that internal customers often use non 2MiB multiples

This IMHO is the main reason to make it an hard failure. A property of good software is that it minimizes surprises.

The kernel already does some THP optimizations if the memory is a multiple of 2MB (e.g. it aligns it automatically), so I want to avoid surface for potentially hard to troubleshoot performance issues.

return int(stdout.strip())


@pytest.mark.parametrize(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we check that the host has THP enabled?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not, the test will crash (like it should because it means we can't really evaluate THP)

anon_huge_kb = get_anon_huge_pages_kb(vm.firecracker_pid)

if huge_pages == HugePagesConfig.TRANSPARENT:
# With THP enabled, the kernel should have promoted some pages (let's say 100 MB out of 128MB)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be fragile? Also, the check below is for 64MiB, not 100MiB (nit: let's use MiB rather than MB).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, but at the same time I think we have to have a test like this to kind of prove THP is doing "something".

Comment thread tests/integration_tests/functional/test_huge_pages.py
@ilstam

ilstam commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Have we concluded the performance analysis on this?

@marco-marangoni

marco-marangoni commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Have we concluded the performance analysis on this?

There are some data on https://buildkite.com/firecracker/mamarang-nested-a-b/builds/110/list (red = performance changed = good), however some tests also failed because of 5.10 false positives (see #6007).

I'm running the tests again on https://buildkite.com/firecracker/mamarang-nested-a-b/builds/114/list


The gains on nested, with 6.1 guest are around 10% build time improvement (no boot time improvement, I'll investigate why).
With 6.18 guest, boottime improves by approx 20% to 75% faster, depending on the exact config.

Baremetal also shows significant bootime improvements with 6.18 guest: 5% to 40% faster


Edit: guest kernel version had no impact sorry. Boot improved only on 6.18, because previous versions don't align memory automatically, but that's for another PR

@marco-marangoni marco-marangoni force-pushed the thp branch 2 times, most recently from 0e73d41 to 004d935 Compare July 1, 2026 13:31
@marco-marangoni marco-marangoni marked this pull request as ready for review July 1, 2026 15:31
@ilstam

ilstam commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Boot improved only on 6.18, because previous versions don't align memory automatically, but that's for another PR

Why is it for another PR? Isn't THP the reason we want to align memory? I think it makes sense to review and merge these together so that performance is consistent across kernel versions if possible.

@marco-marangoni

Copy link
Copy Markdown
Contributor Author

No specific reason to have them in separate PRs, except for size.
I pushed a new commit that also enables guest memory alignment.

Performance tests are running: https://buildkite.com/firecracker/mamarang-nested-a-b/builds/119/list

Results of THP vs THP+alignment are promising in isolation (https://buildkite.com/firecracker/mamarang-nested-a-b/builds/117): another -20 to -75% boot time in nested. But main vs THP+alignment will be more interesting once it completes.

@marco-marangoni marco-marangoni force-pushed the thp branch 4 times, most recently from c5ce18a to 8492a34 Compare July 3, 2026 15:27
Comment thread src/vmm/src/devices/virtio/pmem/device.rs
Comment thread src/vmm/src/vstate/memory.rs Outdated
Comment thread src/vmm/src/vstate/memory.rs Outdated
Comment thread src/vmm/src/vstate/memory.rs Outdated
)
};
if ptr == libc::MAP_FAILED {
return Err(MemoryError::MmapRegionError(MmapRegionError::Mmap(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't really control errors like ENOMEM.

I think I prefer to have it as an error, as a failure doesn't necessarily indicate a mis-behavior in Firecracker

Comment thread src/vmm/src/vstate/memory.rs Outdated
Comment thread src/vmm/src/vstate/memory.rs Outdated
Comment thread src/vmm/src/vstate/memory.rs Outdated
Comment thread src/vmm/src/vstate/memory.rs Outdated
Comment thread src/vmm/src/vstate/memory.rs Outdated
/// or use the [Self::allocate] function instead.
///
/// Note: the returned region of memory might be bigger than the requested size, as it is aligned to page-size.
fn allocate_protected(size: usize) -> Result<Self, MemoryError> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the name mean? Why not allocate_aligned()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"protected" because the resulting memory is PROT_NONE, and further mmap are required to make the memory useful.

I guess allocate_aligned can also work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll stick with allocate_protected. Otherwise, it's not clear why the caller would want to use allocate vs allocate_aligned.

@marco-marangoni marco-marangoni force-pushed the thp branch 2 times, most recently from 2319583 to 998a0ef Compare July 7, 2026 14:08
Comment thread src/vmm/src/utils/mod.rs Outdated
Comment thread src/vmm/src/utils/mod.rs Outdated
Comment thread src/vmm/src/devices/virtio/vhost_user.rs Outdated
Comment thread src/vmm/src/vstate/memory.rs Outdated
Comment thread src/vmm/src/vstate/memory.rs Outdated
Comment thread src/vmm/src/vstate/memory.rs
@marco-marangoni marco-marangoni force-pushed the thp branch 3 times, most recently from d384634 to cd70d79 Compare July 8, 2026 14:56
ShadowCurse
ShadowCurse previously approved these changes Jul 8, 2026
By not using the mmap from vm-memory, the seccomp rule detection for
mmap became more accurate. This highlighted a latent bug where seccomp
rules with masked_eq were not properly evaluated by the Python test.

Signed-off-by: Marco Marangoni <mamarang@amazon.com>
Having align_up and align_down as a macro allows to use if for more
integer types, e.g. usize.

This commit also adds unit tests for the two.

Signed-off-by: Marco Marangoni <mamarang@amazon.com>
This commit adds THP for the guest memory, with a new value for the
huge_pages option.

Signed-off-by: Marco Marangoni <mamarang@amazon.com>
This commit align guest memory to 2MiB.
The main benefit is to enable further opportunities to use THP at the
head and tail of guest memory.

Alignment is enabled unconditionally (rather than only with THP) for a
few reasons: reduce modalities, performance benefit when THP is enforced
 by the system rather than Firecracker (e.g.
 `/sys/kernel/mm/transparent_hugepage/enabled` = `always`), and because
there's no downside of doing so.

This implementation doesn't make assumptions on the guest memory size,
so extra considerations had to be taken when it is not a multiple of
page-size. This is required since several tests currently rely on
guests of specific sizes.

Signed-off-by: Marco Marangoni <mamarang@amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants