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_tx_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
87        .par_iter_edges()
88        .filter(|&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        .collect();
99    print!("| {:>12} ", edges.len()); // n_edge_total
100    let n_e = edges.len();
101    print!("| {n_e:>17} "); // n_edge_to_process
102    let mut nd = map.allocate_used_darts(6 * n_e); // 2 for edge split + 2*2 for new edges in neighbor tets
103    let mut darts: Vec<DartIdType> = (nd..nd + 6 * n_e as DartIdType).into_par_iter().collect();
104    prof_stop!("HCBENCH_CUTS_COMPUTE");
105    print!("| {:>18.6e} ", instant.elapsed().as_secs_f64()); // t_compute_batch
106
107    // while there are edges to cut
108    while !edges.is_empty() {
109        // process batch
110        prof_start!("HCBENCH_CUTS_PROCESS");
111        instant = Instant::now();
112        let n_retry = match args.backend {
113            crate::cli::Backend::RayonIter => dispatch_rayon(&map, &mut edges, &darts),
114            crate::cli::Backend::RayonChunks => {
115                dispatch_rayon_chunks(&map, &mut edges, &darts, n_threads)
116            }
117            crate::cli::Backend::StdThreads => {
118                dispatch_std_threads(&map, &mut edges, &darts, n_threads)
119            }
120        };
121        prof_stop!("HCBENCH_CUTS_PROCESS");
122        print!("| {:>18.6e} ", instant.elapsed().as_secs_f64()); // t_process_batch
123        println!("| {n_retry:>15}",); // n_tx_retry
124
125        (1..map.n_darts() as DartIdType).for_each(|d| {
126            if map.is_free(d) && !map.is_unused(d) {
127                map.release_dart(d).expect("E: unreachable");
128            }
129        });
130
131        // compute the new batch
132        step += 1;
133        print!(" {step:>4} "); // Step
134        prof_start!("HCBENCH_CUTS_COMPUTE");
135        instant = Instant::now();
136        edges.par_extend(map.par_iter_edges().filter(|&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        print!("| {:>12} ", edges.len()); // n_edge_total
147        let n_e = edges.len();
148        print!("| {n_e:>17} "); // n_edge_to_process
149        nd = map.allocate_used_darts(6 * n_e);
150        darts.par_drain(..); // is there a better way?
151        darts.extend(nd..nd + 6 * n_e as DartIdType);
152        prof_stop!("HCBENCH_CUTS_COMPUTE");
153        if n_e != 0 {
154            print!("| {:>18.6e} ", instant.elapsed().as_secs_f64()); // t_compute_batch
155        } else {
156            print!("| {:>18.6e} ", instant.elapsed().as_secs_f64()); // t_compute_batch
157            print!("| {:>18.6e} ", 0.0); // t_process_batch
158            println!("| {:>15}", 0); // n_tx_retry
159        }
160    }
161    prof_stop!("HCBENCH_CUTS");
162
163    map
164}
165
166#[inline]
167fn dispatch_rayon<T: CoordsFloat>(
168    map: &CMap2<T>,
169    edges: &mut Vec<EdgeIdType>,
170    darts: &[DartIdType],
171) -> u32 {
172    let units: Vec<(u32, [u32; 6])> = edges
173        .drain(..)
174        .zip(darts.chunks(6))
175        .map(|(e, sl)| (e, sl.try_into().unwrap()))
176        .collect();
177    units
178        .into_par_iter()
179        .map(|(e, new_darts)| {
180            let mut n_retry = 0;
181            if map.is_i_free::<2>(e as DartIdType) {
182                while !process_outer_edge(map, &mut n_retry, e, new_darts).is_validated() {}
183            } else {
184                while !process_inner_edge(map, &mut n_retry, e, new_darts).is_validated() {}
185            }
186            n_retry as u32
187        }) // par_map
188        .sum()
189}
190
191#[inline]
192fn dispatch_rayon_chunks<T: CoordsFloat>(
193    map: &CMap2<T>,
194    edges: &mut Vec<EdgeIdType>,
195    darts: &[DartIdType],
196    n_threads: usize,
197) -> u32 {
198    let units: Vec<(u32, [u32; 6])> = edges
199        .drain(..)
200        .zip(darts.chunks(6))
201        .map(|(e, sl)| (e, sl.try_into().unwrap()))
202        .collect();
203    units
204        .par_chunks(1 + units.len() / n_threads)
205        .map(|c| {
206            let mut n = 0;
207            c.iter().for_each(|&(e, new_darts)| {
208                let mut n_retry = 0;
209                if map.is_i_free::<2>(e as DartIdType) {
210                    while !process_outer_edge(map, &mut n_retry, e, new_darts).is_validated() {}
211                } else {
212                    while !process_inner_edge(map, &mut n_retry, e, new_darts).is_validated() {}
213                }
214                n += n_retry as u32;
215            });
216            n
217        }) // par_for_each
218        .sum()
219}
220
221#[inline]
222fn dispatch_std_threads<T: CoordsFloat>(
223    map: &CMap2<T>,
224    edges: &mut Vec<EdgeIdType>,
225    darts: &[DartIdType],
226    n_threads: usize,
227) -> u32 {
228    let units: Vec<(u32, [u32; 6])> = edges
229        .drain(..)
230        .zip(darts.chunks(6))
231        .map(|(e, sl)| (e, sl.try_into().unwrap()))
232        .collect();
233
234    #[cfg(feature = "bind-threads")]
235    {
236        use std::sync::Arc;
237
238        use hwlocality::{Topology, cpu::binding::CpuBindingFlags};
239
240        use crate::utils::get_proc_list;
241
242        let topo = Arc::new(Topology::new().unwrap());
243        let mut cores = get_proc_list(&topo).unwrap_or_default().into_iter().cycle();
244        std::thread::scope(|s| {
245            let mut handles = Vec::new();
246            for wl in units.chunks(1 + units.len() / n_threads) {
247                let topo = topo.clone();
248                let core = cores.next();
249                handles.push(s.spawn(move || {
250                    // bind
251                    if let Some(c) = core {
252                        let tid = hwlocality::current_thread_id();
253                        topo.bind_thread_cpu(tid, &c, CpuBindingFlags::empty())
254                            .unwrap();
255                    }
256                    // work
257                    let mut n = 0;
258                    wl.iter().for_each(|&(e, new_darts)| {
259                        let mut n_retry = 0;
260                        if map.is_i_free::<2>(e as DartIdType) {
261                            while !process_outer_edge(map, &mut n_retry, e, new_darts)
262                                .is_validated()
263                            {}
264                        } else {
265                            while !process_inner_edge(map, &mut n_retry, e, new_darts)
266                                .is_validated()
267                            {}
268                        }
269                        n += n_retry as u32;
270                    });
271                    n
272                })); // s.spawn
273            } // for wl in workloads
274            handles.into_iter().map(|h| h.join().unwrap()).sum()
275        }) // std::thread::scope
276    }
277
278    #[cfg(not(feature = "bind-threads"))]
279    {
280        std::thread::scope(|s| {
281            let mut handles = Vec::new();
282            for wl in units.chunks(1 + units.len() / n_threads) {
283                handles.push(s.spawn(|| {
284                    let mut n = 0;
285                    wl.iter().for_each(|&(e, new_darts)| {
286                        let mut n_retry = 0;
287                        if map.is_i_free::<2>(e as DartIdType) {
288                            while !process_outer_edge(map, &mut n_retry, e, new_darts)
289                                .is_validated()
290                            {}
291                        } else {
292                            while !process_inner_edge(map, &mut n_retry, e, new_darts)
293                                .is_validated()
294                            {}
295                        }
296                        n += n_retry as u32;
297                    });
298                    n
299                })); // s.spawn
300            } // for wl in workloads
301            handles.into_iter().map(|h| h.join().unwrap()).sum()
302        }) // std::thread::scope
303    }
304}
305
306#[inline]
307fn process_outer_edge<T: CoordsFloat>(
308    map: &CMap2<T>,
309    n_retry: &mut u8,
310    e: EdgeIdType,
311    [nd1, nd2, nd3, _, _, _]: [DartIdType; 6],
312) -> TransactionResult<(), SewError> {
313    Transaction::with_control_and_err(
314        |_| {
315            *n_retry += 1;
316            TransactionControl::Retry
317        },
318        |t| cut_outer_edge(t, map, e, [nd1, nd2, nd3]),
319    ) // Transaction::with_control
320}
321
322#[inline]
323fn process_inner_edge<T: CoordsFloat>(
324    map: &CMap2<T>,
325    n_retry: &mut u8,
326    e: EdgeIdType,
327    nds: [DartIdType; 6],
328) -> TransactionResult<(), SewError> {
329    Transaction::with_control_and_err(
330        |_| {
331            *n_retry += 1;
332            TransactionControl::Retry
333        },
334        |t| cut_inner_edge(t, map, e, nds),
335    ) // Transaction::with_control
336}