Skip to content

Commit

Permalink
faultlatency: Test latency of THP vs normal page
Browse files Browse the repository at this point in the history
  • Loading branch information
evanj committed Jan 15, 2023
1 parent e0cf875 commit 15eebfd
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 80 deletions.
30 changes: 30 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "hugepagedemo"
version = "0.1.0"
edition = "2021"
default-run = "hugepagedemo"

[profile.release-nativecpu]
inherits = "release"
Expand All @@ -13,6 +14,7 @@ debug = true

[dependencies]
argh = "0.1.9"
go-parse-duration = "0"
humanunits = {git="https://github.com/evanj/humanunits"}
lazy_static = "1"
memory-stats = "1"
Expand All @@ -21,3 +23,4 @@ rand = "0"
rand_xoshiro = "0"
regex = "1"
strum = { version = "0", features = ["derive"] }
time = { version="0", features=["std"]}
9 changes: 2 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,12 @@ all: aligned_alloc_demo
cargo check
# https://zhauniarovich.com/post/2021/2021-09-pedantic-clippy/#paranoid-clippy
# -D clippy::restriction is way too "safe"/careful
# -D clippy::pedantic is also probably too safe: currently allowing things we run into
# -D clippy::pedantic too paranoid
# -A clippy::option-if-let-else: I stylistically disagree with this
cargo clippy --all-targets --all-features -- \
-D warnings \
-D clippy::nursery \
-A clippy::option-if-let-else \
-D clippy::pedantic \
-A clippy::cast_precision_loss \
-A clippy::cast-sign-loss \
-A clippy::cast-possible-truncation \
-A clippy::too-many-lines
-A clippy::option-if-let-else

clang-format -i '-style={BasedOnStyle: Google, ColumnLimit: 100}' *.c

Expand Down
194 changes: 194 additions & 0 deletions src/bin/faultlatency.rs
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);
// }
// }
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod mmaputils;
pub use mmaputils::MmapOwner;
pub use mmaputils::MmapRegion;
Loading

0 comments on commit 15eebfd

Please sign in to comment.