honeycomb_benches/
cut_edges.rs

1//! `cut-edges` benchmark
2//!
3//! This benchmark iteratively insert vertex and edges in cells in a triangular meshes to cut down
4//! edges length down to a target value (expressed as an absolute value). It currently precomputes
5//! all affected edges in order to pre-allocate the darts used in the parallel insertions.
6
7use std::time::Instant;
8
9use rayon::prelude::*;
10
11use honeycomb::{
12    core::{
13        cmap::SewError,
14        stm::{Transaction, TransactionControl, TransactionResult},
15    },
16    kernels::remeshing::{cut_inner_edge, cut_outer_edge},
17    prelude::{CMap2, CMapBuilder, CoordsFloat, DartIdType, EdgeIdType},
18};
19
20use crate::{
21    cli::CutEdgesArgs,
22    prof_start, prof_stop,
23    utils::{get_num_threads, hash_file},
24};
25
26// const MAX_RETRY: u8 = 10;
27
28pub fn bench_cut_edges<T: CoordsFloat>(args: CutEdgesArgs) -> CMap2<T> {
29    let input_map = args.input.to_str().unwrap();
30    let target_len = T::from(args.target_length).unwrap();
31
32    let n_threads = if let Ok(val) = get_num_threads() {
33        val
34    } else {
35        std::thread::available_parallelism()
36            .map(|n| n.get())
37            .unwrap_or(1)
38    };
39
40    // load map from file
41    let mut instant = Instant::now();
42    let input_hash = hash_file(input_map).expect("E: could not compute input hash"); // file id for posterity
43
44    let mut map: CMap2<T> = if input_map.ends_with(".cmap") {
45        CMapBuilder::<2, _>::from_cmap_file(input_map)
46            .build()
47            .unwrap()
48    } else if input_map.ends_with(".vtk") {
49        CMapBuilder::<2, _>::from_vtk_file(input_map)
50            .build()
51            .unwrap()
52    } else {
53        panic!(
54            "E: Unknown file format; only .cmap or .vtk files are supported for map initialization"
55        );
56    };
57    #[cfg(debug_assertions)] // check input
58    {
59        use honeycomb::prelude::OrbitPolicy;
60        assert!(
61            map.iter_faces()
62                .all(|f| { map.orbit(OrbitPolicy::Face, f as DartIdType).count() == 3 }),
63            "Input mesh isn't a triangle mesh"
64        );
65    }
66    println!("| cut-edges benchmark");
67    println!("|-> input      : {input_map} (hash: {input_hash:#0x})",);
68    println!(
69        "|-> backend    : {:?} with {n_threads} thread(s)",
70        args.backend
71    );
72    println!("|-> target size: {target_len:?}");
73    println!("|-> init time  : {}ms", instant.elapsed().as_millis());
74
75    println!(
76        " Step | n_edge_total | n_edge_to_process | t_compute_batch(s) | t_process_batch(s) | n_transac_retry"
77    );
78
79    let mut step = 0;
80    print!(" {step:>4} "); // Step
81    prof_start!("HCBENCH_CUTS");
82
83    // compute first batch
84    prof_start!("HCBENCH_CUTS_COMPUTE");
85    instant = Instant::now();
86    let mut edges: Vec<EdgeIdType> = map.iter_edges().collect();
87    print!("| {:>12} ", edges.len()); // n_edge_total
88    edges.retain(|&e| {
89        let (vid1, vid2) = (
90            map.vertex_id(e as DartIdType),
91            map.vertex_id(map.beta::<1>(e as DartIdType)),
92        );
93        match (map.force_read_vertex(vid1), map.force_read_vertex(vid2)) {
94            (Some(v1), Some(v2)) => (v2 - v1).norm() > target_len,
95            (_, _) => false,
96        }
97    });
98    let n_e = edges.len();
99    print!("| {n_e:>17} "); // n_edge_to_process
100    let mut nd = map.allocate_used_darts(6 * n_e); // 2 for edge split + 2*2 for new edges in neighbor tets
101    let mut darts: Vec<DartIdType> = (nd..nd + 6 * n_e as DartIdType).collect();
102    prof_stop!("HCBENCH_CUTS_COMPUTE");
103    print!("| {:>18.6e} ", instant.elapsed().as_secs_f64()); // t_compute_batch
104
105    // while there are edges to cut
106    while !edges.is_empty() {
107        // process batch
108        prof_start!("HCBENCH_CUTS_PROCESS");
109        instant = Instant::now();
110        let n_retry = match args.backend {
111            crate::cli::Backend::RayonIter => dispatch_rayon(&map, &mut edges, &darts),
112            crate::cli::Backend::RayonChunks => {
113                dispatch_rayon_chunks(&map, &mut edges, &darts, n_threads)
114            }
115            crate::cli::Backend::StdThreads => {
116                dispatch_std_threads(&map, &mut edges, &darts, n_threads)
117            }
118        };
119        prof_stop!("HCBENCH_CUTS_PROCESS");
120        print!("| {:>18.6e} ", instant.elapsed().as_secs_f64()); // t_process_batch
121        println!("| {n_retry:>15}",); // n_transac_retry
122
123        (1..map.n_darts() as DartIdType).for_each(|d| {
124            if map.is_free(d) && !map.is_unused(d) {
125                map.release_dart(d).expect("E: unreachable");
126            }
127        });
128
129        // compute the new batch
130        step += 1;
131        print!(" {step:>4} "); // Step
132        prof_start!("HCBENCH_CUTS_COMPUTE");
133        instant = Instant::now();
134        edges.extend(map.iter_edges());
135        print!("| {:>12} ", edges.len()); // n_edge_total
136        edges.retain(|&e| {
137            let (vid1, vid2) = (
138                map.vertex_id(e as DartIdType),
139                map.vertex_id(map.beta::<1>(e as DartIdType)),
140            );
141            match (map.force_read_vertex(vid1), map.force_read_vertex(vid2)) {
142                (Some(v1), Some(v2)) => (v2 - v1).norm() > target_len,
143                (_, _) => false,
144            }
145        });
146        let n_e = edges.len();
147        print!("| {n_e:>17} "); // n_edge_to_process
148        nd = map.allocate_used_darts(6 * n_e);
149        darts.par_drain(..); // is there a better way?
150        darts.extend(nd..nd + 6 * n_e as DartIdType);
151        prof_stop!("HCBENCH_CUTS_COMPUTE");
152        if n_e != 0 {
153            print!("| {:>18.6e} ", instant.elapsed().as_secs_f64()); // t_compute_batch
154        } else {
155            print!("| {:>18.6e} ", instant.elapsed().as_secs_f64()); // t_compute_batch
156            print!("| {:>18.6e} ", 0.0); // t_process_batch
157            println!("| {:>15}", 0); // n_transac_retry
158        }
159    }
160    prof_stop!("HCBENCH_CUTS");
161
162    map
163}
164
165#[inline]
166fn dispatch_rayon<T: CoordsFloat>(
167    map: &CMap2<T>,
168    edges: &mut Vec<EdgeIdType>,
169    darts: &[DartIdType],
170) -> u32 {
171    let units: Vec<(u32, [u32; 6])> = edges
172        .drain(..)
173        .zip(darts.chunks(6))
174        .map(|(e, sl)| (e, sl.try_into().unwrap()))
175        .collect();
176    units
177        .into_par_iter()
178        .map(|(e, new_darts)| {
179            let mut n_retry = 0;
180            if map.is_i_free::<2>(e as DartIdType) {
181                while !process_outer_edge(map, &mut n_retry, e, new_darts).is_validated() {}
182            } else {
183                while !process_inner_edge(map, &mut n_retry, e, new_darts).is_validated() {}
184            }
185            n_retry as u32
186        }) // par_map
187        .sum()
188}
189
190#[inline]
191fn dispatch_rayon_chunks<T: CoordsFloat>(
192    map: &CMap2<T>,
193    edges: &mut Vec<EdgeIdType>,
194    darts: &[DartIdType],
195    n_threads: usize,
196) -> u32 {
197    let units: Vec<(u32, [u32; 6])> = edges
198        .drain(..)
199        .zip(darts.chunks(6))
200        .map(|(e, sl)| (e, sl.try_into().unwrap()))
201        .collect();
202    units
203        .par_chunks(1 + units.len() / n_threads)
204        .map(|c| {
205            let mut n = 0;
206            c.iter().for_each(|&(e, new_darts)| {
207                let mut n_retry = 0;
208                if map.is_i_free::<2>(e as DartIdType) {
209                    while !process_outer_edge(map, &mut n_retry, e, new_darts).is_validated() {}
210                } else {
211                    while !process_inner_edge(map, &mut n_retry, e, new_darts).is_validated() {}
212                }
213                n += n_retry as u32;
214            });
215            n
216        }) // par_for_each
217        .sum()
218}
219
220#[inline]
221fn dispatch_std_threads<T: CoordsFloat>(
222    map: &CMap2<T>,
223    edges: &mut Vec<EdgeIdType>,
224    darts: &[DartIdType],
225    n_threads: usize,
226) -> u32 {
227    let units: Vec<(u32, [u32; 6])> = edges
228        .drain(..)
229        .zip(darts.chunks(6))
230        .map(|(e, sl)| (e, sl.try_into().unwrap()))
231        .collect();
232
233    #[cfg(feature = "bind-threads")]
234    {
235        use std::sync::Arc;
236
237        use hwlocality::{Topology, cpu::binding::CpuBindingFlags};
238
239        use crate::utils::get_proc_list;
240
241        let topo = Arc::new(Topology::new().unwrap());
242        let mut cores = get_proc_list(&topo).unwrap_or_default().into_iter().cycle();
243        std::thread::scope(|s| {
244            let mut handles = Vec::new();
245            for wl in units.chunks(1 + units.len() / n_threads) {
246                let topo = topo.clone();
247                let core = cores.next();
248                handles.push(s.spawn(move || {
249                    // bind
250                    if let Some(c) = core {
251                        let tid = hwlocality::current_thread_id();
252                        topo.bind_thread_cpu(tid, &c, CpuBindingFlags::empty())
253                            .unwrap();
254                    }
255                    // work
256                    let mut n = 0;
257                    wl.iter().for_each(|&(e, new_darts)| {
258                        let mut n_retry = 0;
259                        if map.is_i_free::<2>(e as DartIdType) {
260                            while !process_outer_edge(map, &mut n_retry, e, new_darts)
261                                .is_validated()
262                            {}
263                        } else {
264                            while !process_inner_edge(map, &mut n_retry, e, new_darts)
265                                .is_validated()
266                            {}
267                        }
268                        n += n_retry as u32;
269                    });
270                    n
271                })); // s.spawn
272            } // for wl in workloads
273            handles.into_iter().map(|h| h.join().unwrap()).sum()
274        }) // std::thread::scope
275    }
276
277    #[cfg(not(feature = "bind-threads"))]
278    {
279        std::thread::scope(|s| {
280            let mut handles = Vec::new();
281            for wl in units.chunks(1 + units.len() / n_threads) {
282                handles.push(s.spawn(|| {
283                    let mut n = 0;
284                    wl.iter().for_each(|&(e, new_darts)| {
285                        let mut n_retry = 0;
286                        if map.is_i_free::<2>(e as DartIdType) {
287                            while !process_outer_edge(map, &mut n_retry, e, new_darts)
288                                .is_validated()
289                            {}
290                        } else {
291                            while !process_inner_edge(map, &mut n_retry, e, new_darts)
292                                .is_validated()
293                            {}
294                        }
295                        n += n_retry as u32;
296                    });
297                    n
298                })); // s.spawn
299            } // for wl in workloads
300            handles.into_iter().map(|h| h.join().unwrap()).sum()
301        }) // std::thread::scope
302    }
303}
304
305#[inline]
306fn process_outer_edge<T: CoordsFloat>(
307    map: &CMap2<T>,
308    n_retry: &mut u8,
309    e: EdgeIdType,
310    [nd1, nd2, nd3, _, _, _]: [DartIdType; 6],
311) -> TransactionResult<(), SewError> {
312    Transaction::with_control_and_err(
313        |_| {
314            *n_retry += 1;
315            TransactionControl::Retry
316        },
317        |trans| cut_outer_edge(trans, map, e, [nd1, nd2, nd3]),
318    ) // Transaction::with_control
319}
320
321#[inline]
322fn process_inner_edge<T: CoordsFloat>(
323    map: &CMap2<T>,
324    n_retry: &mut u8,
325    e: EdgeIdType,
326    nds: [DartIdType; 6],
327) -> TransactionResult<(), SewError> {
328    Transaction::with_control_and_err(
329        |_| {
330            *n_retry += 1;
331            TransactionControl::Retry
332        },
333        |trans| cut_inner_edge(trans, map, e, nds),
334    ) // Transaction::with_control
335}