1#[cfg(feature = "ndarray")]
2use ndarray::prelude::*;
3use rustc_hash::FxHashMap as HashMap;
4
5use super::DMatrix;
6
7pub fn build_name2index_map(_names: &[Box<str>]) -> HashMap<Box<str>, usize> {
8 _names
9 .iter()
10 .enumerate()
11 .map(|(r, name)| (name.clone(), r))
12 .collect()
13}
14
15pub fn take_subset_indices_names(
16 new_indices: &[usize],
17 ntot: usize,
18 old_names: Vec<Box<str>>,
19) -> (HashMap<u64, u64>, Vec<Box<str>>) {
20 let mut old2new: HashMap<u64, u64> = Default::default();
21 let mut new2old = vec![];
22 debug_assert!(ntot == old_names.len());
23 let mut k = 0_u64;
24 for idx in new_indices.iter() {
25 if *idx < ntot {
26 old2new.insert(*idx as u64, k);
27 new2old.push(*idx);
28 k += 1;
29 }
30 }
31
32 let new_names = new2old
33 .iter()
34 .map(|&i| old_names[i].clone())
35 .collect::<Vec<Box<str>>>();
36
37 (old2new, new_names)
38}
39
40pub fn take_subset_indices_names_if_needed(
41 new_indices: Option<&Vec<usize>>,
42 ntot: Option<usize>,
43 old_names: Vec<Box<str>>,
44) -> (HashMap<u64, u64>, Vec<Box<str>>) {
45 let ntot = ntot.unwrap_or(old_names.len());
46 if let Some(new_indices) = new_indices {
47 take_subset_indices_names(new_indices, ntot, old_names)
48 } else {
49 let names = old_names;
50 let identity = (0..(ntot as u64))
51 .zip(0..(ntot as u64))
52 .collect::<HashMap<u64, u64>>();
53 (identity, names)
54 }
55}
56
57#[cfg(feature = "ndarray")]
58pub fn ndarray_to_triplets(array: &Array2<f32>) -> Vec<(u64, u64, f32)> {
59 let eps = 1e-6;
60 array
61 .indexed_iter()
62 .filter(|(_, &elem)| elem.abs() > eps)
63 .map(|((row, col), &value)| (row as u64, col as u64, value))
64 .collect::<Vec<(u64, u64, f32)>>()
65}
66
67pub fn dmatrix_to_triplets(matrix: &DMatrix<f32>) -> Vec<(u64, u64, f32)> {
68 let (nrow, _) = matrix.shape();
69 let eps = 1e-6;
70 matrix
71 .iter() .enumerate()
73 .filter(|(_, &elem)| elem.abs() > eps)
74 .map(|(idx, &value)| {
75 let row = idx % nrow;
76 let col = idx / nrow;
77 (row as u64, col as u64, value)
78 })
79 .collect::<Vec<(u64, u64, f32)>>()
80}
81
82pub fn remove_backend_path(path: &str) -> anyhow::Result<()> {
85 let p = std::path::Path::new(path);
86 if p.exists() {
87 if p.is_file() {
88 std::fs::remove_file(p)?;
89 } else {
90 std::fs::remove_dir_all(p)?;
91 }
92 }
93 Ok(())
94}
95
96static RESERVED: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
98
99const PRELOAD_BYTES_PER_NNZ: usize = 12;
101
102#[derive(Debug)]
109pub struct PreloadReservation {
110 bytes: usize,
111 counter: &'static std::sync::atomic::AtomicUsize,
112}
113
114impl PreloadReservation {
115 #[allow(dead_code)] pub fn bytes(&self) -> usize {
119 self.bytes
120 }
121}
122
123impl Clone for PreloadReservation {
124 fn clone(&self) -> Self {
125 self.counter
126 .fetch_add(self.bytes, std::sync::atomic::Ordering::Relaxed);
127 Self {
128 bytes: self.bytes,
129 counter: self.counter,
130 }
131 }
132}
133
134impl Drop for PreloadReservation {
135 fn drop(&mut self) {
136 self.counter
137 .fetch_sub(self.bytes, std::sync::atomic::Ordering::Relaxed);
138 }
139}
140
141#[allow(dead_code)] pub fn preload_reserved_bytes() -> usize {
144 RESERVED.load(std::sync::atomic::Ordering::Relaxed)
145}
146
147pub fn reserve_preload(nnz: usize, what: &str) -> Option<PreloadReservation> {
160 let cost = nnz.saturating_mul(PRELOAD_BYTES_PER_NNZ);
161 if let Some(budget) = std::env::var("LEGUME_PRELOAD_BUDGET_BYTES")
162 .ok()
163 .and_then(|v| v.parse::<usize>().ok())
164 {
165 return fits_or_warn(cost, budget, nnz, what).then_some(PreloadReservation {
166 bytes: 0,
167 counter: &RESERVED,
168 });
169 }
170 reserve_against(&RESERVED, cost, auto_preload_budget_bytes(), nnz, what)
171}
172
173fn reserve_against(
174 counter: &'static std::sync::atomic::AtomicUsize,
175 cost: usize,
176 budget: usize,
177 nnz: usize,
178 what: &str,
179) -> Option<PreloadReservation> {
180 use std::sync::atomic::Ordering;
181 let reserved = counter.fetch_add(cost, Ordering::Relaxed);
182 if fits_or_warn(reserved.saturating_add(cost), budget, nnz, what) {
183 Some(PreloadReservation {
184 bytes: cost,
185 counter,
186 })
187 } else {
188 counter.fetch_sub(cost, Ordering::Relaxed);
189 None
190 }
191}
192
193#[allow(dead_code)] pub fn preload_within_budget(nnz: usize, what: &str) -> bool {
198 reserve_preload(nnz, what).map(std::mem::forget).is_some()
199}
200
201fn fits_or_warn(cost: usize, budget: usize, nnz: usize, what: &str) -> bool {
202 if cost > budget {
203 log::warn!(
204 "skipping {what} preload of {nnz} nnz: {cost} bytes would exceed the \
205 {budget}-byte budget (LEGUME_PRELOAD_BUDGET_BYTES to raise); reads stay on the \
206 streaming path"
207 );
208 false
209 } else {
210 true
211 }
212}
213
214fn auto_preload_budget_bytes() -> usize {
216 static BUDGET: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
217 *BUDGET.get_or_init(|| {
218 const FALLBACK_BYTES: usize = 8 << 30;
219 let mut sys = sysinfo::System::new();
220 sys.refresh_memory();
221 match usize::try_from(sys.available_memory()) {
222 Ok(avail) if avail > 0 => avail / 2,
223 _ => FALLBACK_BYTES,
224 }
225 })
226}
227
228#[allow(dead_code)] pub fn auto_preload_enabled() -> bool {
234 !matches!(
235 std::env::var("LEGUME_AUTO_PRELOAD").as_deref(),
236 Ok("0") | Ok("false") | Ok("off")
237 )
238}
239
240pub const SLAB_BUDGET_BYTES: usize = 256 << 20;
245pub const TRIPLET_BYTES: usize = 24;
247
248#[cfg(test)]
249mod preload_reservation_tests {
250 use super::*;
251 use std::sync::atomic::{AtomicUsize, Ordering};
252
253 #[test]
254 fn dropping_a_reservation_gives_it_back() {
255 static COUNTER: AtomicUsize = AtomicUsize::new(0);
256 let a = reserve_against(&COUNTER, 60, 100, 5, "test").expect("fits");
257 assert_eq!(COUNTER.load(Ordering::Relaxed), 60);
258 assert!(reserve_against(&COUNTER, 60, 100, 5, "test").is_none());
261 assert_eq!(COUNTER.load(Ordering::Relaxed), 60);
262 drop(a);
263 assert_eq!(COUNTER.load(Ordering::Relaxed), 0);
264 let b = reserve_against(&COUNTER, 60, 100, 5, "test").expect("fits after release");
266 assert_eq!(b.bytes(), 60);
267 drop(b);
268 assert_eq!(COUNTER.load(Ordering::Relaxed), 0);
269 }
270
271 #[test]
272 fn cloning_a_reservation_reserves_again() {
273 static COUNTER: AtomicUsize = AtomicUsize::new(0);
274 let a = reserve_against(&COUNTER, 30, 100, 5, "test").expect("fits");
275 let b = a.clone();
276 assert_eq!(COUNTER.load(Ordering::Relaxed), 60);
277 drop(a);
278 assert_eq!(COUNTER.load(Ordering::Relaxed), 30);
279 drop(b);
280 assert_eq!(COUNTER.load(Ordering::Relaxed), 0);
281 }
282}