use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CacheSizes {
pub l1_data_bytes: usize,
pub l1_line_bytes: usize,
pub l2_bytes: usize,
pub l3_bytes: usize,
}
impl CacheSizes {
pub const fn default_conservative() -> Self {
Self {
l1_data_bytes: 32 * 1024, l1_line_bytes: 64, l2_bytes: 256 * 1024, l3_bytes: 8 * 1024 * 1024, }
}
}
impl Default for CacheSizes {
fn default() -> Self {
Self::default_conservative()
}
}
pub fn detect_cache_sizes() -> CacheSizes {
static CACHE_SIZES: OnceLock<CacheSizes> = OnceLock::new();
*CACHE_SIZES.get_or_init(|| {
#[cfg(target_arch = "x86_64")]
{
detect_x86_64_cache_sizes()
}
#[cfg(target_arch = "aarch64")]
{
detect_aarch64_cache_sizes()
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
CacheSizes::default_conservative()
}
})
}
#[cfg(target_arch = "x86_64")]
fn detect_x86_64_cache_sizes() -> CacheSizes {
use std::arch::x86_64::__cpuid;
unsafe {
let cpuid_max = __cpuid(0);
if cpuid_max.eax >= 0x04 {
if let Some(sizes) = detect_intel_deterministic_cache() {
return sizes;
}
}
let cpuid_ext_max = __cpuid(0x80000000);
if cpuid_ext_max.eax >= 0x80000006 {
let cpuid = __cpuid(0x80000006);
let l1_data_kb = ((cpuid.ecx >> 24) & 0xFF) as usize;
let l1_line = (cpuid.ecx & 0xFF) as usize;
let l2_kb = ((cpuid.ecx >> 16) & 0xFFFF) as usize;
let l3_units = ((cpuid.edx >> 18) & 0x3FFF) as usize;
let l3_kb = l3_units * 512;
if (16..=128).contains(&l1_data_kb) {
return CacheSizes {
l1_data_bytes: l1_data_kb * 1024,
l1_line_bytes: if l1_line == 64 || l1_line == 128 { l1_line } else { 64 },
l2_bytes: if l2_kb > 0 && l2_kb < 2048 { l2_kb * 1024 } else { 256 * 1024 },
l3_bytes: if l3_kb > 0 && l3_kb < 64 * 1024 { l3_kb * 1024 } else { 8 * 1024 * 1024 },
};
}
}
CacheSizes::default_conservative()
}
}
#[cfg(target_arch = "x86_64")]
unsafe fn detect_intel_deterministic_cache() -> Option<CacheSizes> {
use std::arch::x86_64::__cpuid_count;
let mut l1_data = 0;
let mut l1_line = 64;
let mut l2 = 0;
let mut l3 = 0;
for i in 0..32 {
let cpuid = __cpuid_count(0x04, i);
let cache_type = cpuid.eax & 0x1F;
if cache_type == 0 {
break; }
let level = (cpuid.eax >> 5) & 0x07;
let line_size = (cpuid.ebx & 0xFFF) + 1;
let partitions = ((cpuid.ebx >> 12) & 0x3FF) + 1;
let ways = ((cpuid.ebx >> 22) & 0x3FF) + 1;
let sets = cpuid.ecx + 1;
let size = (ways * partitions * line_size * sets) as usize;
match (level, cache_type) {
(1, 1) => { l1_data = size;
l1_line = line_size as usize;
},
(2, 3) => l2 = size, (3, 3) => l3 = size, _ => {}
}
}
if l1_data >= 16 * 1024 {
Some(CacheSizes {
l1_data_bytes: l1_data,
l1_line_bytes: l1_line,
l2_bytes: if l2 > 0 { l2 } else { 256 * 1024 },
l3_bytes: if l3 > 0 { l3 } else { 8 * 1024 * 1024 },
})
} else {
None
}
}
#[cfg(target_arch = "aarch64")]
fn detect_aarch64_cache_sizes() -> CacheSizes {
#[cfg(target_os = "linux")]
{
if let Some(sizes) = read_linux_sysfs_cache() {
return sizes;
}
}
CacheSizes {
l1_data_bytes: 64 * 1024, l1_line_bytes: 128, l2_bytes: 4 * 1024 * 1024, l3_bytes: 32 * 1024 * 1024, }
}
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
fn read_linux_sysfs_cache() -> Option<CacheSizes> {
use std::fs;
let base_path = "/sys/devices/system/cpu/cpu0/cache";
let l1_data = fs::read_to_string(format!("{}/index0/size", base_path))
.ok()
.and_then(|s| parse_cache_size(&s))?;
let l1_line = fs::read_to_string(format!("{}/index0/coherency_line_size", base_path))
.ok()
.and_then(|s| s.trim().parse().ok())?;
let l2 = fs::read_to_string(format!("{}/index2/size", base_path))
.ok()
.and_then(|s| parse_cache_size(&s))?;
let l3 = fs::read_to_string(format!("{}/index3/size", base_path))
.ok()
.and_then(|s| parse_cache_size(&s))
.unwrap_or(8 * 1024 * 1024);
Some(CacheSizes {
l1_data_bytes: l1_data,
l1_line_bytes: l1_line,
l2_bytes: l2,
l3_bytes: l3,
})
}
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
fn parse_cache_size(s: &str) -> Option<usize> {
let s = s.trim();
if s.ends_with('K') {
s[..s.len() - 1].parse::<usize>().ok().map(|v| v * 1024)
} else if s.ends_with('M') {
s[..s.len() - 1].parse::<usize>().ok().map(|v| v * 1024 * 1024)
} else {
s.parse().ok()
}
}
pub fn optimal_partition_size_for_cache(_k: usize) -> usize {
let cache = detect_cache_sizes();
let l1_per_partition = cache.l1_data_bytes / 4;
let optimal_bits = l1_per_partition * 8;
const MIN_PARTITION_BITS: usize = 512; const MAX_PARTITION_BITS: usize = 256 * 1024 * 8;
optimal_bits.clamp(MIN_PARTITION_BITS, MAX_PARTITION_BITS)
}
pub fn recommend_partition_size(total_bits: usize, k: usize) -> usize {
let cache = detect_cache_sizes();
let base_partition = total_bits.div_ceil(k);
let cache_optimal = optimal_partition_size_for_cache(k);
if base_partition <= cache_optimal {
return base_partition;
}
if base_partition > cache.l2_bytes * 8 {
eprintln!(
"Warning: Partition size {} KB exceeds L2 cache {} KB. Consider using standard Bloom filter.",
base_partition / 8192,
cache.l2_bytes / 1024
);
}
(cache.l2_bytes * 8).min(base_partition)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_cache_sizes() {
let sizes = detect_cache_sizes();
println!("Detected cache sizes:");
println!(" L1 data: {} KB", sizes.l1_data_bytes / 1024);
println!(" L1 line: {} bytes", sizes.l1_line_bytes);
println!(" L2: {} KB", sizes.l2_bytes / 1024);
println!(" L3: {} MB", sizes.l3_bytes / (1024 * 1024));
assert!(sizes.l1_data_bytes >= 16 * 1024); assert!(sizes.l1_data_bytes <= 128 * 1024); assert!(sizes.l1_line_bytes == 64 || sizes.l1_line_bytes == 128);
assert!(sizes.l2_bytes >= 128 * 1024); assert!(sizes.l3_bytes >= 1 * 1024 * 1024); }
#[test]
fn test_optimal_partition_size() {
let k = 7;
let size = optimal_partition_size_for_cache(k);
println!("Optimal partition size for k={}: {} KB",
k, size / 8192);
assert!(size >= 512); assert!(size <= 256 * 1024 * 8); }
#[test]
fn test_recommend_partition_size() {
let total_bits = 1_000_000;
let k = 7;
let recommended = recommend_partition_size(total_bits, k);
println!("Recommended partition size: {} KB", recommended / 8192);
assert!(recommended > 0);
assert!(recommended <= total_bits);
}
#[test]
fn test_cache_detection_cached() {
let sizes1 = detect_cache_sizes();
let sizes2 = detect_cache_sizes();
assert_eq!(sizes1, sizes2);
}
#[test]
fn test_large_filter_warning() {
let total_bits = 1_000_000_000; let k = 7;
let _ = recommend_partition_size(total_bits, k);
}
}