1use 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
26pub 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 let mut instant = Instant::now();
42 let input_hash = hash_file(input_map).expect("E: could not compute input hash"); 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)] {
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} "); prof_start!("HCBENCH_CUTS");
82
83 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()); let n_e = edges.len();
101 print!("| {n_e:>17} "); let mut nd = map.allocate_used_darts(6 * n_e); 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()); while !edges.is_empty() {
109 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()); println!("| {n_retry:>15}",); (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 step += 1;
133 print!(" {step:>4} "); 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()); let n_e = edges.len();
148 print!("| {n_e:>17} "); nd = map.allocate_used_darts(6 * n_e);
150 darts.par_drain(..); 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()); } else {
156 print!("| {:>18.6e} ", instant.elapsed().as_secs_f64()); print!("| {:>18.6e} ", 0.0); println!("| {:>15}", 0); }
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 }) .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 }) .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 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 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 })); } handles.into_iter().map(|h| h.join().unwrap()).sum()
275 }) }
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 })); } handles.into_iter().map(|h| h.join().unwrap()).sum()
302 }) }
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 ) }
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 ) }