honeycomb_benches/
utils.rs

1use std::collections::hash_map::DefaultHasher;
2use std::fs::File;
3use std::hash::Hasher;
4use std::io::Read;
5
6#[cfg(feature = "bind-threads")]
7use hwlocality::{
8    Topology,
9    cpu::cpuset::CpuSet,
10    object::types::ObjectType,
11    topology::support::{DiscoverySupport, FeatureSupport},
12};
13
14cfg_if::cfg_if! {
15    if #[cfg(feature = "_single_precision")] {
16        /// Floating-point type alias.
17        ///
18        /// This is mostly used to run tests using both `f64` and `f32`.
19        pub type FloatType = f32;
20    } else {
21        /// Floating-point type alias.
22        ///
23        /// This is mostly used to run tests using both `f64` and `f32`.
24        pub type FloatType = f64;
25    }
26}
27
28pub fn hash_file(path: &str) -> Result<u64, std::io::Error> {
29    let mut file = File::open(path)?;
30    let mut hasher = DefaultHasher::new();
31    let mut buffer = [0; 8192];
32
33    loop {
34        let bytes_read = file.read(&mut buffer)?;
35        if bytes_read == 0 {
36            break;
37        }
38        hasher.write(&buffer[..bytes_read]);
39    }
40
41    Ok(hasher.finish())
42}
43
44pub const NUM_THREADS_VAR: &str = "RAYON_NUM_THREADS";
45
46pub fn get_num_threads() -> Result<usize, String> {
47    match std::env::var(NUM_THREADS_VAR) {
48        Ok(val) => val.parse::<usize>().map_err(|e| e.to_string()),
49        Err(e) => Err(e.to_string()),
50    }
51}
52
53#[cfg(feature = "bind-threads")]
54#[derive(Debug, Default)]
55pub enum BindingPolicy {
56    /// Disable thread binding.
57    /// Corresponding `RAYON_PROC_BIND_VALUE`: `false`.
58    Disable,
59    /// Enable thread binding & prioritize binding of PUs over cores?.
60    /// Corresponding `RAYON_PROC_BIND_VALUE`: `close`.
61    Close,
62    /// Enable thread binding & prioritize binding across cores over filling PUs?.
63    /// Corresponding `RAYON_PROC_BIND_VALUE`: `spread`. Default value.
64    #[default]
65    Spread,
66}
67
68/// Environment variable controlling the thread-binding policy.
69///
70/// The name of this variable and its possible values reflect the OpenMP equivalents.
71#[cfg(feature = "bind-threads")]
72pub const RAYON_PROC_BIND_VAR: &str = "RAYON_PROC_BIND";
73
74#[cfg(feature = "bind-threads")]
75impl BindingPolicy {
76    fn from_env() -> Self {
77        match std::env::var(RAYON_PROC_BIND_VAR) {
78            Ok(val) => match val.to_lowercase().as_str() {
79                "false" => Self::Disable,
80                "close" => Self::Close,
81                "spread" => Self::Spread,
82                "" => Self::default(),
83                _ => {
84                    eprintln!("W: unrecognized RAYON_PROC_BIND value (!= false | close | spread)");
85                    eprintln!("   continuing with default (spread)");
86                    Self::default()
87                }
88            },
89            Err(e) => {
90                match e {
91                    std::env::VarError::NotPresent => {}
92                    std::env::VarError::NotUnicode(_) => {
93                        eprintln!("W: non-unicode RAYON_PROC_BIND value");
94                        eprintln!("   continuing with default (spread)");
95                    }
96                }
97                Self::default()
98            }
99        }
100    }
101}
102
103#[cfg(feature = "bind-threads")]
104pub fn check_hwloc_support(topology: &Topology) -> Result<(), String> {
105    if !topology.supports(FeatureSupport::discovery, DiscoverySupport::pu_count) {
106        return Err("missing PU reporting support".to_string());
107    }
108    if !topology
109        .feature_support()
110        .cpu_binding()
111        .is_some_and(|s| s.get_thread() && s.set_thread())
112    {
113        return Err("missing binding support".to_string());
114    }
115
116    Ok(())
117}
118
119/// Return a list of bind targets ordered according to the desired policy.
120///
121/// The desired policy is read from an environment variable (see [`RAYON_PROC_BIND_VAR`]). For details on each policy,
122/// see [`BindingPolicy`].
123///
124/// The returned list is used by iterating over a `cycle`d version of it, which corresponds to a round robin logic.
125#[cfg(feature = "bind-threads")]
126pub fn get_proc_list(topology: &Topology) -> Option<Vec<CpuSet>> {
127    let binding_policy = BindingPolicy::from_env();
128    let core_depth = topology
129        .depth_or_below_for_type(ObjectType::Core)
130        .expect("E: unreachable");
131
132    match binding_policy {
133        BindingPolicy::Disable => None,
134        BindingPolicy::Close => {
135            let mut pu_set = Vec::with_capacity(256);
136            topology.objects_at_depth(core_depth).for_each(|c| {
137                let target = c.cpuset().unwrap().clone_target();
138                let w = target.weight();
139                if !(w == Some(1) || w == Some(2)) {
140                    panic!()
141                }
142                target
143                    .iter_set()
144                    .map(CpuSet::from)
145                    .for_each(|t| pu_set.push(t));
146            });
147            Some(pu_set)
148        }
149        BindingPolicy::Spread => {
150            let mut first_pu_set = Vec::with_capacity(128);
151            let mut second_pu_set = Vec::with_capacity(128);
152            topology.objects_at_depth(core_depth).for_each(|c| {
153                let target = c.cpuset().expect("E: unreachable").clone_target();
154                // match required bc some modern CPUs have HT/SMT on only some of their cores :)
155                match target.weight() {
156                    Some(1) => {
157                        // one PU per core -> no HT/SMT
158                        first_pu_set.push(target);
159                    }
160                    Some(2) => {
161                        // two PUs per core -> HT/SMT
162                        let [first_pu, second_pu]: [CpuSet; 2] = target
163                            .iter_set()
164                            .map(CpuSet::from)
165                            .collect::<Vec<_>>()
166                            .try_into()
167                            .expect("E: unreachable");
168                        first_pu_set.push(first_pu);
169                        second_pu_set.push(second_pu);
170                    }
171                    Some(_) | None => {
172                        panic!("E: architecture too cursed")
173                    }
174                }
175            });
176            first_pu_set.append(&mut second_pu_set);
177            Some(first_pu_set)
178        }
179    }
180}
181
182#[cfg(feature = "profiling")]
183pub static mut PERF_FIFO: Option<File> = None;
184
185/// Attempt to open a fifo at the path `/tmp/hc_perf_control`.
186///
187/// This macro doesn't generate any code if the `profiling` feature is disabled.
188#[macro_export]
189macro_rules! prof_init {
190    () => {
191        #[cfg(feature = "profiling")]
192        {
193            unsafe {
194                $crate::utils::PERF_FIFO = Some(
195                    std::fs::OpenOptions::new()
196                        .write(true)
197                        .open("/tmp/hc_perf_control")
198                        .expect("Failed to open FIFO"),
199                );
200            }
201        }
202    };
203}
204
205/// Write to the `/tmp/hc_perf_control` to enable perf sampling if `${$var}` is defined.
206///
207/// This macro doesn't generate any code if the `profiling` feature is disabled.
208#[macro_export]
209macro_rules! prof_start {
210    ($var: literal) => {
211        #[cfg(feature = "profiling")]
212        {
213            // use an env variable to select profiled section
214            if std::env::var_os($var).is_some() {
215                use std::io::Write;
216                unsafe {
217                    if let Some(ref mut f) = $crate::utils::PERF_FIFO {
218                        f.write_all(b"enable\n")
219                            .expect("E: failed to write to FIFO");
220                        f.flush().expect("E: failed to flush FIFO");
221                    }
222                }
223            }
224        }
225    };
226}
227
228/// Write to the `/tmp/hc_perf_control` to disable perf sampling if `${$var}` is defined.
229///
230/// This macro doesn't generate any code if the `profiling` feature is disabled.
231#[macro_export]
232macro_rules! prof_stop {
233    ($var: literal) => {
234        #[cfg(feature = "profiling")]
235        {
236            // use an env variable to select profiled section
237            if std::env::var_os($var).is_some() {
238                use std::io::Write;
239                unsafe {
240                    if let Some(ref mut f) = $crate::utils::PERF_FIFO {
241                        f.write_all(b"disable\n")
242                            .expect("E: failed to write to FIFO");
243                        f.flush().expect("E: failed to flush FIFO");
244                    }
245                }
246            }
247        }
248    };
249}