#[cfg(target_os = "linux")]
use std::fs::OpenOptions;
#[cfg(target_os = "linux")]
use std::io::Write;
#[cfg(target_os = "linux")]
use std::os::fd::AsFd;
use std::ptr::NonNull;
#[derive(Debug, Clone, Copy)]
pub struct HugePageConfig {
pub enabled: bool,
pub min_size: usize,
pub fallback: bool,
}
impl Default for HugePageConfig {
fn default() -> Self {
Self {
enabled: cfg!(target_os = "linux"), min_size: 2 * 1024 * 1024, fallback: true,
}
}
}
pub fn alloc_huge(size: usize, config: HugePageConfig) -> Option<*mut u8> {
if !config.enabled || size < config.min_size {
return alloc_normal(size);
}
#[cfg(target_os = "linux")]
{
alloc_linux_huge(size, config.fallback)
}
#[cfg(not(target_os = "linux"))]
{
if config.fallback {
alloc_normal(size)
} else {
None
}
}
}
pub unsafe fn dealloc_huge(ptr: *mut u8, size: usize, config: HugePageConfig) {
if !config.enabled || size < config.min_size {
dealloc_normal(ptr, size);
return;
}
#[cfg(target_os = "linux")]
{
dealloc_normal(ptr, size);
}
#[cfg(not(target_os = "linux"))]
{
dealloc_normal(ptr, size);
}
}
fn alloc_normal(size: usize) -> Option<*mut u8> {
use std::alloc::{alloc, Layout};
let aligned_size = (size + 63) & !63;
let layout = Layout::from_size_align(aligned_size, 64).ok()?;
let ptr = unsafe { alloc(layout) };
NonNull::new(ptr).map(|p| p.as_ptr())
}
unsafe fn dealloc_normal(ptr: *mut u8, size: usize) {
use std::alloc::{dealloc, Layout};
let aligned_size = (size + 63) & !63;
let layout = Layout::from_size_align_unchecked(aligned_size, 64);
dealloc(ptr, layout);
}
#[cfg(target_os = "linux")]
fn alloc_linux_huge(size: usize, fallback: bool) -> Option<*mut u8> {
use std::alloc::Layout;
use std::ptr;
if let Ok(_) = reserve_huge_pages(size) {
let ptr = unsafe {
libc::mmap(
ptr::null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_HUGETLB,
-1,
0,
)
};
if ptr != libc::MAP_FAILED {
return Some(ptr as *mut u8);
}
}
if fallback {
alloc_normal(size)
} else {
None
}
}
#[cfg(target_os = "linux")]
fn reserve_huge_pages(size: usize) -> Result<(), std::io::Error> {
use std::path::Path;
let huge_pages_needed = (size + 2 * 1024 * 1024 - 1) / (2 * 1024 * 1024);
if Path::new("/proc/sys/vm/nr_hugepages").exists() {
let mut file = OpenOptions::new()
.write(true)
.open("/proc/sys/vm/nr_hugepages")?;
writeln!(file, "{}", huge_pages_needed)?;
}
Ok(())
}
pub fn huge_pages_available() -> bool {
#[cfg(target_os = "linux")]
{
std::path::Path::new("/proc/sys/vm/nr_hugepages").exists()
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
pub fn huge_page_size() -> Option<usize> {
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/proc/meminfo") {
for line in content.lines() {
if line.starts_with("Hugepagesize:") {
if let Some(kb_str) = line.split_whitespace().nth(1) {
if let Ok(kb) = kb_str.parse::<usize>() {
return Some(kb * 1024);
}
}
}
}
}
None
}
#[cfg(not(target_os = "linux"))]
{
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_huge_page_config() {
let config = HugePageConfig::default();
assert!(config.enabled == cfg!(target_os = "linux"));
assert_eq!(config.min_size, 2 * 1024 * 1024);
assert!(config.fallback);
}
#[test]
fn test_normal_allocation() {
let size = 1024;
let ptr = alloc_normal(size);
assert!(!ptr.unwrap().is_null());
unsafe {
dealloc_normal(ptr.unwrap(), size);
}
}
#[test]
fn test_huge_page_info() {
let _available = huge_pages_available();
let _size = huge_page_size();
}
#[test]
fn test_huge_page_fallback() {
let config = HugePageConfig {
enabled: true,
min_size: 2 * 1024 * 1024,
fallback: true,
};
let ptr = alloc_huge(1024, config);
assert!(ptr.is_some());
unsafe {
dealloc_huge(ptr.unwrap(), 1024, config);
}
let ptr = alloc_huge(4 * 1024 * 1024, config);
assert!(ptr.is_some());
unsafe {
dealloc_huge(ptr.unwrap(), 4 * 1024 * 1024, config);
}
}
}