-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
faultlatency: Test latency of THP vs normal page
- Loading branch information
Showing
7 changed files
with
318 additions
and
80 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
use hugepagedemo::MmapRegion; | ||
use nix::sys::mman::MmapAdvise; | ||
use std::{ | ||
error::Error, | ||
ffi::c_void, | ||
time::{Duration, Instant}, | ||
}; | ||
use time::OffsetDateTime; | ||
|
||
#[derive(argh::FromArgs)] | ||
/// Control the options for fault latency testing. | ||
struct FaultLatencyOptions { | ||
/// probe the page latency at this interval. | ||
#[argh( | ||
option, | ||
default = "Duration::from_secs(1)", | ||
from_str_fn(argh_parse_go_duration) | ||
)] | ||
test_interval: Duration, | ||
|
||
/// sleep duration between probing the different page sizes. | ||
#[argh( | ||
option, | ||
default = "Duration::from_millis(100)", | ||
from_str_fn(argh_parse_go_duration) | ||
)] | ||
sleep_between_page_sizes: Duration, | ||
} | ||
|
||
/// Parses a duration using Go's formats, with the signature required by argh. | ||
fn argh_parse_go_duration(s: &str) -> Result<Duration, String> { | ||
let result = go_parse_duration::parse_duration(s); | ||
match result { | ||
Err(err) => Err(format!("{err:?}")), | ||
Ok(nanos) => { | ||
assert!(nanos >= 0); | ||
Ok(Duration::from_nanos( | ||
nanos.try_into().expect("BUG: duration must be >= 0"), | ||
)) | ||
} | ||
} | ||
} | ||
|
||
struct FaultLatency { | ||
mmap: Duration, | ||
fault: Duration, | ||
second_write: Duration, | ||
} | ||
|
||
impl FaultLatency { | ||
const fn new(mmap: Duration, fault: Duration, second_write: Duration) -> Self { | ||
Self { | ||
mmap, | ||
fault, | ||
second_write, | ||
} | ||
} | ||
} | ||
|
||
fn fault_4kib() -> Result<FaultLatency, nix::errno::Errno> { | ||
const PAGE_4KIB: usize = 4 << 10; | ||
|
||
let start = Instant::now(); | ||
let region = MmapRegion::new(PAGE_4KIB)?; | ||
let mmap_end = Instant::now(); | ||
let u64_pointer = region.get_mut().cast::<u64>(); | ||
unsafe { | ||
*u64_pointer = 0x42; | ||
} | ||
let fault_end = Instant::now(); | ||
unsafe { | ||
*u64_pointer = 0x43; | ||
} | ||
let second_write_end = Instant::now(); | ||
|
||
Ok(FaultLatency::new( | ||
mmap_end - start, | ||
fault_end - mmap_end, | ||
second_write_end - fault_end, | ||
)) | ||
} | ||
|
||
fn fault_2mib() -> Result<FaultLatency, nix::errno::Errno> { | ||
const PAGE_2MIB: usize = 2 << 20; | ||
|
||
let start = Instant::now(); | ||
let mut region = MmapMadviseNoUnmap::new(PAGE_2MIB)?; | ||
let mmap_end = Instant::now(); | ||
let u64_pointer = region.get_mut().cast::<u64>(); | ||
unsafe { | ||
*u64_pointer = 0x42; | ||
} | ||
let fault_end = Instant::now(); | ||
unsafe { | ||
*u64_pointer = 0x43; | ||
} | ||
let second_write_end = Instant::now(); | ||
|
||
Ok(FaultLatency::new( | ||
mmap_end - start, | ||
fault_end - mmap_end, | ||
second_write_end - fault_end, | ||
)) | ||
} | ||
|
||
fn main() -> Result<(), Box<dyn Error>> { | ||
let config: FaultLatencyOptions = argh::from_env(); | ||
|
||
let mut next = Instant::now() + config.test_interval; | ||
loop { | ||
// sleep first: the first maps/faults after a sleep are slower? | ||
// I suspect weirdness due to CPU power saving states etc | ||
std::thread::sleep(next - Instant::now()); | ||
next += config.test_interval; | ||
|
||
let timing_4kib = fault_4kib()?; | ||
std::thread::sleep(config.sleep_between_page_sizes); | ||
let timing_2mib = fault_2mib()?; | ||
|
||
let wallnow = OffsetDateTime::now_utc(); | ||
println!( | ||
"{wallnow} 4kiB: mmap:{:?} fault:{:?} second_write:{:?}; 2MiB: mmap:{:?} fault:{:?} second_write:{:?}", | ||
timing_4kib.mmap, timing_4kib.fault, timing_4kib.second_write, | ||
timing_2mib.mmap, timing_2mib.fault, timing_2mib.second_write, | ||
); | ||
|
||
// std::thread::sleep(next - Instant::now()); | ||
// next = next + config.test_interval; | ||
} | ||
} | ||
|
||
/// Allocates a memory region with mmap that is aligned with a specific alignment. This can be used | ||
/// for huge page alignment. It allocates a region of size + alignment, then munmaps the extra. | ||
/// Unfortunately, the Linux kernel seems to prefer returning | ||
struct MmapMadviseNoUnmap { | ||
_region: MmapRegion, | ||
aligned_pointer: *mut c_void, | ||
} | ||
|
||
impl MmapMadviseNoUnmap { | ||
fn new(size: usize) -> Result<Self, nix::errno::Errno> { | ||
const ALIGNMENT_2MIB: usize = 2 << 20; | ||
|
||
// worse case alignment: mmap returns 1 byte off the alignment, we must waste alignment-1 bytes. | ||
// To ensure we can do this, we request size+alignment bytes. | ||
// This shouldn't be so bad: untouched pages won't actually be allocated. | ||
let align_rounded_size = size + ALIGNMENT_2MIB; | ||
let region = MmapRegion::new(align_rounded_size)?; | ||
|
||
// Calculate the aligned block, preferring the HIGHEST aligned address, | ||
// since the kernel seems to allocate consecutive allocations downward. | ||
// This allows consecutive calls to mmap to be contiguous, which MIGHT | ||
// allow the kernel to coalesce them into huge pages? Not sure. | ||
let allocation_end = region.get_mut() as usize + align_rounded_size; | ||
let aligned_pointer = | ||
align_pointer_value_down(ALIGNMENT_2MIB, allocation_end - size) as *mut c_void; | ||
|
||
assert!(region.get_mut() <= aligned_pointer); | ||
assert!(aligned_pointer as usize + size <= allocation_end); | ||
|
||
unsafe { | ||
nix::sys::mman::madvise(aligned_pointer, size, MmapAdvise::MADV_HUGEPAGE) | ||
.expect("BUG: madvise must succeed"); | ||
} | ||
|
||
Ok(Self { | ||
_region: region, | ||
aligned_pointer, | ||
}) | ||
} | ||
|
||
pub(crate) fn get_mut(&mut self) -> *mut c_void { | ||
self.aligned_pointer | ||
} | ||
} | ||
|
||
fn align_pointer_value_down(alignment: usize, pointer_value: usize) -> usize { | ||
// see bit hacks to check if power of two: | ||
// https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 | ||
assert_eq!(0, (alignment & (alignment - 1))); | ||
// round pointer_value down to nearest alignment; assumes there is sufficient space | ||
let alignment_mask = !(alignment - 1); | ||
pointer_value & alignment_mask | ||
} | ||
|
||
// #[cfg(test)] | ||
// mod test { | ||
// use super::*; | ||
|
||
// #[test] | ||
// fn test_align_pointer_value() { | ||
// assert_eq!(1, 2); | ||
// } | ||
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
mod mmaputils; | ||
pub use mmaputils::MmapOwner; | ||
pub use mmaputils::MmapRegion; |
Oops, something went wrong.