1#![deny(unsafe_code)]
8
9mod alloc;
10pub use alloc::SmallCache;
11#[doc(hidden)]
12pub use alloc::small_cache_simulate_numa;
13
14pub fn tune_allocator() {
24 #[cfg(all(target_os = "linux", target_env = "gnu"))]
25 {
26 let (cpu_node, l2_share) = topology::detect();
27 alloc::configure(&cpu_node, l2_share);
28 const MMAP_THRESHOLD: libc::c_int = 256 * 1024;
30 const ARENA_MAX: libc::c_int = 4;
31 #[allow(unsafe_code)]
33 unsafe {
34 libc::mallopt(libc::M_MMAP_THRESHOLD, MMAP_THRESHOLD);
35 libc::mallopt(libc::M_ARENA_MAX, ARENA_MAX);
36 }
37 }
38}
39
40pub fn trim_memory() {
42 #[cfg(all(target_os = "linux", target_env = "gnu"))]
43 {
44 #[allow(unsafe_code)]
45 unsafe {
46 libc::malloc_trim(0);
47 }
48 }
49}
50
51#[cfg(all(target_os = "linux", target_env = "gnu"))]
53mod topology {
54 use std::fs;
55
56 const SYS: &str = "/sys/devices/system";
57
58 pub(crate) fn detect() -> (Vec<(usize, usize)>, Option<usize>) {
59 let mut cpu_node = Vec::new();
60 let nodes = read(&format!("{SYS}/node/online")).map_or_else(Vec::new, |s| cpu_list(&s));
61 if nodes.len() > 1 {
62 for node in nodes {
63 if let Some(cpus) = read(&format!("{SYS}/node/node{node}/cpulist")) {
64 cpu_node.extend(cpu_list(&cpus).into_iter().map(|cpu| (cpu, node)));
65 }
66 }
67 }
68 (cpu_node, l2_share())
69 }
70
71 fn l2_share() -> Option<usize> {
73 for index in 0.. {
74 let dir = format!("{SYS}/cpu/cpu0/cache/index{index}");
75 let level = read(&format!("{dir}/level"))?;
76 if level.trim() != "2" {
77 continue;
78 }
79 let size = size(&read(&format!("{dir}/size"))?)?;
80 let sharing = read(&format!("{dir}/shared_cpu_list"))
81 .map_or(1, |s| cpu_list(&s).len())
82 .max(1);
83 return Some(size / sharing);
84 }
85 None
86 }
87
88 fn read(path: &str) -> Option<String> {
89 fs::read_to_string(path).ok()
90 }
91
92 pub(crate) fn size(s: &str) -> Option<usize> {
94 let s = s.trim();
95 let (digits, unit) = match s.as_bytes().last()? {
96 b'K' => (&s[..s.len() - 1], 1 << 10),
97 b'M' => (&s[..s.len() - 1], 1 << 20),
98 b'G' => (&s[..s.len() - 1], 1 << 30),
99 _ => (s, 1),
100 };
101 digits.parse::<usize>().ok().map(|n| n * unit)
102 }
103
104 pub(crate) fn cpu_list(s: &str) -> Vec<usize> {
106 let mut out = Vec::new();
107 for part in s.trim().split(',').filter(|p| !p.is_empty()) {
108 let mut ends = part.splitn(2, '-').map(|n| n.trim().parse::<usize>());
109 match (ends.next(), ends.next()) {
110 (Some(Ok(a)), None) => out.push(a),
111 (Some(Ok(a)), Some(Ok(b))) if a <= b => out.extend(a..=b),
112 _ => {}
113 }
114 }
115 out
116 }
117
118 #[cfg(test)]
119 mod tests {
120 use super::*;
121
122 #[test]
123 fn sysfs_lists_and_sizes_parse() {
124 assert_eq!(cpu_list("0-3,8,10-11\n"), vec![0, 1, 2, 3, 8, 10, 11]);
125 assert_eq!(cpu_list("0"), vec![0]);
126 assert_eq!(cpu_list(""), Vec::<usize>::new());
127 assert_eq!(size("1024K\n"), Some(1 << 20));
128 assert_eq!(size("2M"), Some(2 << 20));
129 assert_eq!(size("512"), Some(512));
130 assert_eq!(size("x"), None);
131 }
132
133 #[test]
134 fn this_machine_reports_an_l2_share() {
135 let (_, l2) = detect();
136 if std::path::Path::new("/sys/devices/system/cpu/cpu0/cache/index2").exists() {
137 assert!(l2.is_some_and(|b| b >= 4096), "{l2:?}");
138 }
139 }
140 }
141}