use std::hint::black_box;
use std::num::Wrapping;
use std::path::Path;
use std::{io, slice};
#[cfg(unix)]
use memmap2::UncheckedAdvice;
use serde::Deserialize;
static ADVICE: parking_lot::RwLock<Advice> = parking_lot::RwLock::new(Advice::Random);
pub fn set_global(advice: Advice) {
*ADVICE.write() = advice;
}
pub fn get_global() -> Advice {
*ADVICE.read()
}
#[derive(Copy, Clone, Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Advice {
Normal,
Random,
Sequential,
}
#[cfg(unix)]
impl From<Advice> for memmap2::Advice {
fn from(advice: Advice) -> Self {
match advice {
Advice::Normal => memmap2::Advice::Normal,
Advice::Random => memmap2::Advice::Random,
Advice::Sequential => memmap2::Advice::Sequential,
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum AdviceSetting {
Global,
Advice(Advice),
}
impl From<Advice> for AdviceSetting {
fn from(advice: Advice) -> Self {
AdviceSetting::Advice(advice)
}
}
impl AdviceSetting {
pub fn resolve(self) -> Advice {
match self {
AdviceSetting::Global => get_global(),
AdviceSetting::Advice(advice) => advice,
}
}
}
pub fn madvise(madviseable: &impl Madviseable, advice: Advice) -> io::Result<()> {
madviseable.madvise(advice)
}
pub trait Madviseable {
fn madvise(&self, advice: Advice) -> io::Result<()> {
#[cfg(unix)]
self.advise_impl(advice.into())?;
#[cfg(not(unix))]
log::debug!("Madvice {advice:?} is ignored on non-unix platforms");
Ok(())
}
#[cfg(unix)]
fn advise_impl(&self, advice: memmap2::Advice) -> io::Result<()>;
fn populate(&self) {
if crate::common::low_memory::low_memory_mode().skip_populate() {
return;
}
#[cfg(target_os = "linux")]
{
use std::sync::LazyLock;
static POPULATE_READ_IS_SUPPORTED: LazyLock<bool> =
LazyLock::new(|| memmap2::Advice::PopulateRead.is_supported());
if *POPULATE_READ_IS_SUPPORTED {
match self.advise_impl(memmap2::Advice::PopulateRead) {
Ok(()) => return,
Err(err) => log::warn!(
"Failed to populate with MADV_POPULATE_READ: {err}. \
Falling back to naive approach."
),
}
}
}
self.populate_simple_impl();
}
fn populate_simple_impl(&self);
fn clear_cache(&self) {
#[cfg(target_os = "linux")]
{
use std::sync::LazyLock;
static PAGEOUT_IS_SUPPORTED: LazyLock<bool> = LazyLock::new(|| {
let res =
unsafe { nix::libc::madvise(std::ptr::null_mut(), 0, nix::libc::MADV_PAGEOUT) };
res == 0
});
if *PAGEOUT_IS_SUPPORTED {
self.pageout_impl();
}
}
}
unsafe fn drop_page_tables(&self, diag_path: &Path) {
#[cfg(not(unix))]
let _ = diag_path;
#[cfg(unix)]
if let Err(e) = unsafe { self.unchecked_advise_impl(UncheckedAdvice::DontNeed) } {
log::warn!("Failed to call madvise(MADV_DONTNEED) for {diag_path:?}: {e}");
}
}
#[cfg(unix)]
unsafe fn unchecked_advise_impl(&self, advice: UncheckedAdvice) -> io::Result<()>;
#[cfg(target_os = "linux")]
fn pageout_impl(&self);
}
#[cfg(target_os = "linux")]
fn pageout_slice(slice: &[u8]) {
if slice.is_empty() {
return;
}
let res = unsafe {
nix::libc::madvise(
slice.as_ptr() as *mut _,
slice.len(),
nix::libc::MADV_PAGEOUT,
)
};
if res != 0 {
let err = io::Error::last_os_error();
log::warn!("Failed to call madvise(MADV_PAGEOUT): {err}");
}
}
impl Madviseable for memmap2::Mmap {
#[cfg(unix)]
fn advise_impl(&self, advice: memmap2::Advice) -> io::Result<()> {
self.advise(advice)
}
fn populate_simple_impl(&self) {
populate_simple(self);
}
#[cfg(unix)]
unsafe fn unchecked_advise_impl(&self, advice: UncheckedAdvice) -> io::Result<()> {
unsafe { self.unchecked_advise(advice) }
}
#[cfg(target_os = "linux")]
fn pageout_impl(&self) {
pageout_slice(self);
}
}
impl Madviseable for memmap2::MmapMut {
#[cfg(unix)]
fn advise_impl(&self, advice: memmap2::Advice) -> io::Result<()> {
self.advise(advice)
}
fn populate_simple_impl(&self) {
populate_simple(self);
}
#[cfg(unix)]
unsafe fn unchecked_advise_impl(&self, advice: UncheckedAdvice) -> io::Result<()> {
unsafe { self.unchecked_advise(advice) }
}
#[cfg(target_os = "linux")]
fn pageout_impl(&self) {
pageout_slice(self);
}
}
impl Madviseable for memmap2::MmapRaw {
#[cfg(unix)]
fn advise_impl(&self, advice: memmap2::Advice) -> io::Result<()> {
self.advise(advice)
}
fn populate_simple_impl(&self) {
let mmap = unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) };
populate_simple(mmap);
}
#[cfg(unix)]
unsafe fn unchecked_advise_impl(&self, advice: UncheckedAdvice) -> io::Result<()> {
unsafe { self.unchecked_advise(advice) }
}
#[cfg(target_os = "linux")]
fn pageout_impl(&self) {
let mmap = unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) };
pageout_slice(mmap);
}
}
fn populate_simple(slice: &[u8]) {
black_box(
slice
.iter()
.copied()
.map(Wrapping)
.step_by(512)
.sum::<Wrapping<u8>>(),
);
}
#[cfg(unix)]
pub fn will_need_multiple_pages(region: &[u8]) {
let Some(page_mask) = page_size().map(|s| s - 1) else {
return;
};
let addr = region.as_ptr().map_addr(|addr| addr & !page_mask);
let length = region.len() + (region.as_ptr().addr() & page_mask);
if length <= page_mask {
return;
}
let res = unsafe { nix::libc::madvise(addr as *mut _, length, nix::libc::MADV_WILLNEED) };
if res != 0 {
#[cfg(debug_assertions)]
{
let err = io::Error::last_os_error();
panic!("Failed to call madvise(MADV_WILLNEED): {err}");
}
}
}
#[cfg(not(unix))]
pub fn will_need_multiple_pages(_region: &[u8]) {}
#[cfg(unix)]
pub fn page_size() -> Option<usize> {
*CACHED_PAGE_SIZE
}
#[cfg(unix)]
static CACHED_PAGE_SIZE: std::sync::LazyLock<Option<usize>> =
std::sync::LazyLock::new(|| get_page_size().inspect_err(|err| log::warn!("{err}")).ok());
#[cfg(unix)]
fn get_page_size() -> Result<usize, String> {
let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE)
.map_err(|err| format!("Failed to get page size: {err}"))?
.ok_or_else(|| "sysconf(PAGE_SIZE) returned None".to_string())?;
let page_size = usize::try_from(page_size)
.map_err(|_| format!("Failed to convert page size {page_size} to usize"))?;
if !page_size.is_power_of_two() {
return Err(format!("Page size {page_size} is not a power of two"));
}
Ok(page_size)
}