From 40268ad19b7bd43dfe793d345b5f18ad2cdb9f73 Mon Sep 17 00:00:00 2001 From: Omar Sobh Date: Sat, 19 Sep 2026 15:59:19 -0500 Subject: [PATCH] =?UTF-8?q?embedded3=20PERF-3=20P1-1:=20the=20moving=20pat?= =?UTF-8?q?h's=20host=20work,=20digit-identical=20=E2=80=94=20impose=5Ffro?= =?UTF-8?q?m's=20solid=20faces=20by=20z=20plane=20in=20parallel=20(1.95=20?= =?UTF-8?q?->=200.62=20s=20per=20step=20at=2011.6=20M=20cells),=20the=20f3?= =?UTF-8?q?2=20fine=20export=20derived=20from=20the=20f64=20level=20the=20?= =?UTF-8?q?refresh=20already=20builds=20(export=5Ffine=201.04=20->=200.34?= =?UTF-8?q?=20s);=20sub-laps=20in=20the=20mask=20rebuild;=20rebuild=20bloc?= =?UTF-8?q?k=209.4=20->=207.4=20s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- .../incompressible/embedded3/impose.rs | 102 ++++++++++-------- .../embedded3/poisson/device_cg.rs | 2 +- .../embedded3/poisson/export.rs | 34 ++++++ .../incompressible/embedded3/step/moving.rs | 10 +- 4 files changed, 102 insertions(+), 46 deletions(-) diff --git a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/impose.rs b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/impose.rs index 1cda0a7..74a98b6 100644 --- a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/impose.rs +++ b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/impose.rs @@ -5,6 +5,7 @@ use super::body::Body; use super::wall::{FaceKind, Mask}; +use rayon::prelude::*; /// The imposition band of a cut mask, in cells. pub(crate) const IMPOSE_BAND_CELLS: f64 = 4.0; @@ -52,55 +53,70 @@ impl Mask { Some(c) => (&c.d_u, &c.d_v, &c.d_w), None => (&[], &[], &[]), }; - for k in 0..nz { - for j in 0..ny { - for i in 1..nx { - let idx = g.uface(k, j, i); - if self.u_kind[idx] == FaceKind::Solid && near(d_u, idx) { - u[idx] = body - .surface_velocity( - i as f64 * dx, - (j as f64 + 0.5) * dy, - (k as f64 + 0.5) * dz, - t, - ) - .0; + // One z plane per task: every solid face is written from the body's + // surface velocity alone (no reduction), so the parallel loop is + // digit-identical to the serial one (PERF-3 P1-1: 1.95 s of a 9.4 s + // step at 11.6 M cells, serial). + u.par_chunks_mut(ny * (nx + 1)) + .take(nz) + .enumerate() + .for_each(|(k, plane)| { + for j in 0..ny { + for i in 1..nx { + let idx = g.uface(k, j, i); + if self.u_kind[idx] == FaceKind::Solid && near(d_u, idx) { + plane[j * (nx + 1) + i] = body + .surface_velocity( + i as f64 * dx, + (j as f64 + 0.5) * dy, + (k as f64 + 0.5) * dz, + t, + ) + .0; + } } } - } - for j in 1..ny { - for i in 0..nx { - let idx = g.vface(k, j, i); - if self.v_kind[idx] == FaceKind::Solid && near(d_v, idx) { - v[idx] = body - .surface_velocity( - (i as f64 + 0.5) * dx, - j as f64 * dy, - (k as f64 + 0.5) * dz, - t, - ) - .1; + }); + v.par_chunks_mut((ny + 1) * nx) + .take(nz) + .enumerate() + .for_each(|(k, plane)| { + for j in 1..ny { + for i in 0..nx { + let idx = g.vface(k, j, i); + if self.v_kind[idx] == FaceKind::Solid && near(d_v, idx) { + plane[j * nx + i] = body + .surface_velocity( + (i as f64 + 0.5) * dx, + j as f64 * dy, + (k as f64 + 0.5) * dz, + t, + ) + .1; + } } } - } - } - for k in 0..=nz { - for j in 0..ny { - for i in 0..nx { - let idx = g.wface(k, j, i); - if self.w_kind[idx] == FaceKind::Solid && near(d_w, idx) { - w[idx] = body - .surface_velocity( - (i as f64 + 0.5) * dx, - (j as f64 + 0.5) * dy, - k as f64 * dz, - t, - ) - .2; + }); + w.par_chunks_mut(ny * nx) + .take(nz + 1) + .enumerate() + .for_each(|(k, plane)| { + for j in 0..ny { + for i in 0..nx { + let idx = g.wface(k, j, i); + if self.w_kind[idx] == FaceKind::Solid && near(d_w, idx) { + plane[j * nx + i] = body + .surface_velocity( + (i as f64 + 0.5) * dx, + (j as f64 + 0.5) * dy, + k as f64 * dz, + t, + ) + .2; + } } } - } - } + }); let u_vals: Vec = self .u_ghosts .iter() diff --git a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/poisson/device_cg.rs b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/poisson/device_cg.rs index 2de4f58..414e761 100644 --- a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/poisson/device_cg.rs +++ b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/poisson/device_cg.rs @@ -255,7 +255,7 @@ impl DeviceCg { let l_key = lap.elapsed(); // The V-cycle's finest level follows the operator (its coarser // levels stay): the fine level alone, no hierarchy build. - let fine_level = super::export::export_fine(problem); + let fine_level = super::export::export_fine_from(&fine); let l_export = lap.elapsed(); self.vcycle.refresh_fine(&fine_level); if profile { diff --git a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/poisson/export.rs b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/poisson/export.rs index a978091..f72b3ef 100644 --- a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/poisson/export.rs +++ b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/poisson/export.rs @@ -125,6 +125,40 @@ pub fn export_fine(problem: &Problem) -> LevelExport { } } +/// As [`export_fine`], from the f64 fine level a caller already built +/// (PERF-3 P1-1): the f32 level is the same masked coefficients cast, so this +/// is the identical export without a second clone of the problem and a +/// second level build. +pub fn export_fine_from(lv: &Level) -> LevelExport { + let (_, coarse_of) = lv.coarsen(); + let to_u32 = |v: &[usize]| { + v.iter() + .map(|&i| if i == usize::MAX { u32::MAX } else { i as u32 }) + .collect::>() + }; + let cast = |v: &[f64]| v.iter().map(|&x| x as f32).collect::>(); + LevelExport { + nx: lv.problem.nx, + ny: lv.problem.ny, + nz: lv.problem.nz, + cells: to_u32(&lv.cells), + red: to_u32(&lv.red), + black: to_u32(&lv.black), + top: to_u32(&lv.top), + bot: to_u32(&lv.bot), + coarse_of: to_u32(&coarse_of), + children_ptr: Vec::new(), + children_idx: Vec::new(), + ae: cast(&lv.ae), + aw: cast(&lv.aw), + an: cast(&lv.an), + as_: cast(&lv.as_), + at: cast(&lv.at), + ab: cast(&lv.ab), + ap: cast(&lv.ap), + } +} + /// `z = M⁻¹ r` by the host f32 V-cycle: the reference a device V-cycle is measured against. pub fn vcycle_f32_reference( problem: &Problem, diff --git a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/moving.rs b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/moving.rs index dec66d8..9b7b739 100644 --- a/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/moving.rs +++ b/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/step/moving.rs @@ -51,6 +51,7 @@ impl Solver { } } let l_step = lap.elapsed(); + let sub = std::time::Instant::now(); new_mask.impose_from( body, &field.u_old, @@ -61,6 +62,7 @@ impl Solver { &mut field.w, t_new, ); + let s_impose = sub.elapsed(); if new_mask.cut().is_some() { let (table, correction) = match &self.mask { Some(old_mask) => new_mask.gcl_flux_table(old_mask, dt), @@ -69,6 +71,7 @@ impl Solver { self.wall_fluxes = table; self.last_ghost_correction = correction; } + let s_gcl = sub.elapsed(); if let Some(old) = &self.mask { self.apertures_old = old .cut() @@ -86,10 +89,13 @@ impl Solver { if std::env::var("RTX_E3_MOVING_PROFILE").is_ok() { let ms = |d: std::time::Duration| d.as_secs_f64() * 1e3; eprintln!( - " mask laps: build_mask {:.0} ms, refill + step apertures + merging {:.0} ms, impose + GCL + volumes {:.0} ms", + " mask laps: build_mask {:.0} ms, refill + step apertures + merging {:.0} ms, impose + GCL + volumes {:.0} ms (impose {:.0}, GCL table {:.0}, old apertures + volumes {:.0})", ms(l_build), ms(l_step - l_build), - ms(lap.elapsed() - l_step) + ms(lap.elapsed() - l_step), + ms(s_impose), + ms(s_gcl - s_impose), + ms(sub.elapsed() - s_gcl) ); } self.mask = Some(new_mask);