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 #[allow(unsafe_code)]
32 unsafe {
33 libc::mallopt(libc::M_MMAP_THRESHOLD, MMAP_THRESHOLD);
34 libc::mallopt(libc::M_ARENA_MAX, arena_max(available_cpus()));
35 }
36 }
37}
38
39#[cfg(all(target_os = "linux", target_env = "gnu"))]
44fn arena_max(cpus: usize) -> libc::c_int {
45 const VALIDATED_ARENAS: usize = 4;
47 const VALIDATED_CPUS: usize = 16; let arenas = cpus
49 .div_ceil(VALIDATED_CPUS / VALIDATED_ARENAS)
50 .max(VALIDATED_ARENAS);
51 libc::c_int::try_from(arenas).unwrap_or(libc::c_int::MAX)
52}
53
54#[cfg(all(target_os = "linux", target_env = "gnu"))]
56fn available_cpus() -> usize {
57 std::thread::available_parallelism().map_or(1, |n| n.get())
58}
59
60pub fn trim_memory() {
62 #[cfg(all(target_os = "linux", target_env = "gnu"))]
63 {
64 #[allow(unsafe_code)]
65 unsafe {
66 libc::malloc_trim(0);
67 }
68 }
69}
70
71#[cfg(all(target_os = "linux", target_env = "gnu"))]
73mod topology {
74 use std::fs;
75
76 const SYS: &str = "/sys/devices/system";
77
78 pub(crate) fn detect() -> (Vec<(usize, usize)>, Option<usize>) {
79 let mut cpu_node = Vec::new();
80 let nodes = read(&format!("{SYS}/node/online")).map_or_else(Vec::new, |s| cpu_list(&s));
81 if nodes.len() > 1 {
82 for node in nodes {
83 if let Some(cpus) = read(&format!("{SYS}/node/node{node}/cpulist")) {
84 cpu_node.extend(cpu_list(&cpus).into_iter().map(|cpu| (cpu, node)));
85 }
86 }
87 }
88 (cpu_node, l2_share())
89 }
90
91 fn l2_share() -> Option<usize> {
93 for index in 0.. {
94 let dir = format!("{SYS}/cpu/cpu0/cache/index{index}");
95 let level = read(&format!("{dir}/level"))?;
96 if level.trim() != "2" {
97 continue;
98 }
99 let size = size(&read(&format!("{dir}/size"))?)?;
100 let sharing = read(&format!("{dir}/shared_cpu_list"))
101 .map_or(1, |s| cpu_list(&s).len())
102 .max(1);
103 return Some(size / sharing);
104 }
105 None
106 }
107
108 fn read(path: &str) -> Option<String> {
109 fs::read_to_string(path).ok()
110 }
111
112 pub(crate) fn size(s: &str) -> Option<usize> {
114 let s = s.trim();
115 let (digits, unit) = match s.as_bytes().last()? {
116 b'K' => (&s[..s.len() - 1], 1 << 10),
117 b'M' => (&s[..s.len() - 1], 1 << 20),
118 b'G' => (&s[..s.len() - 1], 1 << 30),
119 _ => (s, 1),
120 };
121 digits.parse::<usize>().ok().map(|n| n * unit)
122 }
123
124 pub(crate) fn cpu_list(s: &str) -> Vec<usize> {
126 let mut out = Vec::new();
127 for part in s.trim().split(',').filter(|p| !p.is_empty()) {
128 let mut ends = part.splitn(2, '-').map(|n| n.trim().parse::<usize>());
129 match (ends.next(), ends.next()) {
130 (Some(Ok(a)), None) => out.push(a),
131 (Some(Ok(a)), Some(Ok(b))) if a <= b => out.extend(a..=b),
132 _ => {}
133 }
134 }
135 out
136 }
137
138 #[cfg(test)]
139 mod tests {
140 use super::*;
141
142 #[test]
143 fn sysfs_lists_and_sizes_parse() {
144 assert_eq!(cpu_list("0-3,8,10-11\n"), vec![0, 1, 2, 3, 8, 10, 11]);
145 assert_eq!(cpu_list("0"), vec![0]);
146 assert_eq!(cpu_list(""), Vec::<usize>::new());
147 assert_eq!(size("1024K\n"), Some(1 << 20));
148 assert_eq!(size("2M"), Some(2 << 20));
149 assert_eq!(size("512"), Some(512));
150 assert_eq!(size("x"), None);
151 }
152
153 #[test]
154 fn this_machine_reports_an_l2_share() {
155 let (_, l2) = detect();
156 if std::path::Path::new("/sys/devices/system/cpu/cpu0/cache/index2").exists() {
157 assert!(l2.is_some_and(|b| b >= 4096), "{l2:?}");
158 }
159 }
160 }
161}
162
163#[cfg(test)]
164#[cfg(all(target_os = "linux", target_env = "gnu"))]
165mod tests {
166 #[test]
167 fn arenas_keep_the_validated_ratio_and_floor() {
168 for cpus in [1, 2, 4, 8, 15, 16] {
170 assert_eq!(super::arena_max(cpus), 4, "{cpus}");
171 }
172 assert_eq!(super::arena_max(17), 5);
173 assert_eq!(super::arena_max(192), 48);
174 assert_eq!(super::arena_max(384), 96);
175 }
176}