PERF-2 P0: intra-step profiler — StepTimers on the overset solver (RTX_PROFILE; overlap build / predictors / patch BiCGSTAB / background Poisson with its setup-vs-iterate split / round exchange / apply / end exchange), PoissonSolution carries setup_ns and iterate_ns, the harness times advance / restore / snapshot / load sampling / force / FEA predictor and prints the wall split of the coupled phase; no clock is read when profiling is off; the reclassified/step progress denominator fixed (was ×4)
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Format Check (push) Failing after 6s
CI / Build (ubuntu-latest) (push) Failing after 5s
CI / Clippy Check (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 6s
Performance Benchmarks / Run Benchmarks (push) Failing after 27s
CI / Build CPU-Only (Explicit) (push) Failing after 1m24s
Documentation / Build API Documentation (push) Failing after 1m34s
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Format Check (push) Failing after 6s
CI / Build (ubuntu-latest) (push) Failing after 5s
CI / Clippy Check (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 6s
Performance Benchmarks / Run Benchmarks (push) Failing after 27s
CI / Build CPU-Only (Explicit) (push) Failing after 1m24s
Documentation / Build API Documentation (push) Failing after 1m34s
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01YJPeT6WA2e7YvAnS875AHL
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
2d2fed7181
commit
172a26dee4
@@ -6,7 +6,7 @@
|
|||||||
use super::{CurvilinearPisoSolver, PatchField, PressureSystem, SideBc, StepGeometry};
|
use super::{CurvilinearPisoSolver, PatchField, PressureSystem, SideBc, StepGeometry};
|
||||||
use crate::mesh::PatchSide;
|
use crate::mesh::PatchSide;
|
||||||
use crate::solvers::incompressible::sparse_bicgstab::{
|
use crate::solvers::incompressible::sparse_bicgstab::{
|
||||||
bicgstab_jacobi, project_mean, BicgstabResult, CsrMatrix,
|
BicgstabResult, CsrMatrix, bicgstab_jacobi, project_mean,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl CurvilinearPisoSolver {
|
impl CurvilinearPisoSolver {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ mod projection;
|
|||||||
use super::ale::{AleBoundaries, SideBoundary};
|
use super::ale::{AleBoundaries, SideBoundary};
|
||||||
use super::embedded_body::{EmbeddedBody, EmbeddedMask, FaceKind};
|
use super::embedded_body::{EmbeddedBody, EmbeddedMask, FaceKind};
|
||||||
use super::poisson::{
|
use super::poisson::{
|
||||||
solve_multigrid_pcg, MgPrecision, MultigridParameters, PoissonProblem, PoissonSolverKind,
|
MgPrecision, MultigridParameters, PoissonProblem, PoissonSolverKind, solve_multigrid_pcg,
|
||||||
};
|
};
|
||||||
use super::simple::ConvectionScheme;
|
use super::simple::ConvectionScheme;
|
||||||
use super::{FlowField, SolverResult};
|
use super::{FlowField, SolverResult};
|
||||||
@@ -168,6 +168,9 @@ pub struct EmbeddedPisoSolver {
|
|||||||
/// (measured: at 1e-2 the first corrector's rounds never settled below
|
/// (measured: at 1e-2 the first corrector's rounds never settled below
|
||||||
/// a 1e-3 relative change — 20/20 rounds every step).
|
/// a 1e-3 relative change — 20/20 rounds every step).
|
||||||
inner_stop_factor: f64,
|
inner_stop_factor: f64,
|
||||||
|
/// PERF-2 P0: Poisson `(setup ns, iterate ns, calls)` summed over
|
||||||
|
/// every multigrid-PCG solve (`docs/perf2_campaign.md`).
|
||||||
|
poisson_profile: std::cell::Cell<(u64, u64, u64)>,
|
||||||
moving: bool,
|
moving: bool,
|
||||||
/// Mask hysteresis band in multiples of the min cell size (0 = off).
|
/// Mask hysteresis band in multiples of the min cell size (0 = off).
|
||||||
mask_hysteresis: f64,
|
mask_hysteresis: f64,
|
||||||
@@ -194,6 +197,7 @@ impl EmbeddedPisoSolver {
|
|||||||
fringe: None,
|
fringe: None,
|
||||||
fringe_correction: Vec::new(),
|
fringe_correction: Vec::new(),
|
||||||
inner_stop_factor: 1e-2,
|
inner_stop_factor: 1e-2,
|
||||||
|
poisson_profile: std::cell::Cell::new((0, 0, 0)),
|
||||||
moving: false,
|
moving: false,
|
||||||
mask_hysteresis: 0.0,
|
mask_hysteresis: 0.0,
|
||||||
time: 0.0,
|
time: 0.0,
|
||||||
@@ -290,6 +294,11 @@ impl EmbeddedPisoSolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Relative part of the pressure solve's inner stop (see the field).
|
/// Relative part of the pressure solve's inner stop (see the field).
|
||||||
|
/// The Poisson solves' `(setup ns, iterate ns, calls)` so far.
|
||||||
|
pub fn poisson_profile(&self) -> (u64, u64, u64) {
|
||||||
|
self.poisson_profile.get()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_inner_stop_factor(&mut self, factor: f64) {
|
pub fn set_inner_stop_factor(&mut self, factor: f64) {
|
||||||
self.inner_stop_factor = factor;
|
self.inner_stop_factor = factor;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
//! meshes before applying the correction once.
|
//! meshes before applying the correction once.
|
||||||
|
|
||||||
use super::EmbeddedPisoSolver;
|
use super::EmbeddedPisoSolver;
|
||||||
|
use crate::CfdResult;
|
||||||
use crate::solvers::incompressible::ale::SideBoundary;
|
use crate::solvers::incompressible::ale::SideBoundary;
|
||||||
use crate::solvers::incompressible::poisson::{
|
use crate::solvers::incompressible::poisson::{
|
||||||
solve_multigrid_pcg, MultigridParameters, PoissonProblem, PoissonSolverKind,
|
MultigridParameters, PoissonProblem, PoissonSolverKind, solve_multigrid_pcg,
|
||||||
};
|
};
|
||||||
use crate::solvers::incompressible::{EmbeddedMask, FlowField};
|
use crate::solvers::incompressible::{EmbeddedMask, FlowField};
|
||||||
use crate::CfdResult;
|
|
||||||
|
|
||||||
impl EmbeddedPisoSolver {
|
impl EmbeddedPisoSolver {
|
||||||
/// The pressure-correction system of one projection as a
|
/// The pressure-correction system of one projection as a
|
||||||
@@ -304,6 +304,9 @@ impl EmbeddedPisoSolver {
|
|||||||
inner_stop,
|
inner_stop,
|
||||||
anchor_cell,
|
anchor_cell,
|
||||||
);
|
);
|
||||||
|
let (s0, i0, c0) = self.poisson_profile.get();
|
||||||
|
self.poisson_profile
|
||||||
|
.set((s0 + solution.setup_ns, i0 + solution.iterate_ns, c0 + 1));
|
||||||
// Unconverged: fall back to the SOR sweeps for this projection
|
// Unconverged: fall back to the SOR sweeps for this projection
|
||||||
// rather than apply a correction that did not reach the stop.
|
// rather than apply a correction that did not reach the stop.
|
||||||
multigrid_converged = solution.converged;
|
multigrid_converged = solution.converged;
|
||||||
@@ -337,44 +340,28 @@ impl EmbeddedPisoSolver {
|
|||||||
// prescribed: a domain side with velocity data, or a
|
// prescribed: a domain side with velocity data, or a
|
||||||
// non-fluid interior face.
|
// non-fluid interior face.
|
||||||
let ae = if i + 1 == nx {
|
let ae = if i + 1 == nx {
|
||||||
if b.right == outlet {
|
if b.right == outlet { ae_outlet } else { 0.0 }
|
||||||
ae_outlet
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
} else if self.u_is_fluid(j, i + 1) {
|
} else if self.u_is_fluid(j, i + 1) {
|
||||||
ae_interior
|
ae_interior
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
let aw = if i == 0 {
|
let aw = if i == 0 {
|
||||||
if b.left == outlet {
|
if b.left == outlet { ae_outlet } else { 0.0 }
|
||||||
ae_outlet
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
} else if self.u_is_fluid(j, i) {
|
} else if self.u_is_fluid(j, i) {
|
||||||
ae_interior
|
ae_interior
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
let an = if j + 1 == ny {
|
let an = if j + 1 == ny {
|
||||||
if b.top == outlet {
|
if b.top == outlet { an_outlet } else { 0.0 }
|
||||||
an_outlet
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
} else if self.v_is_fluid(j + 1, i) {
|
} else if self.v_is_fluid(j + 1, i) {
|
||||||
an_interior
|
an_interior
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
let as_ = if j == 0 {
|
let as_ = if j == 0 {
|
||||||
if b.bottom == outlet {
|
if b.bottom == outlet { an_outlet } else { 0.0 }
|
||||||
an_outlet
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
} else if self.v_is_fluid(j, i) {
|
} else if self.v_is_fluid(j, i) {
|
||||||
an_interior
|
an_interior
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -322,11 +322,7 @@ pub fn polygon_signed_distance(vertices: &[(f64, f64)], x: f64, y: f64) -> f64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let dist = dist2.sqrt();
|
let dist = dist2.sqrt();
|
||||||
if inside {
|
if inside { -dist } else { dist }
|
||||||
-dist
|
|
||||||
} else {
|
|
||||||
dist
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Velocity of the point on a closed polygon nearest to `(x, y)`, where
|
/// Velocity of the point on a closed polygon nearest to `(x, y)`, where
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ pub use curvilinear::{
|
|||||||
};
|
};
|
||||||
pub use embedded::{EmbeddedParameters, EmbeddedPisoSolver, EmbeddedResult, EmbeddedSolverState};
|
pub use embedded::{EmbeddedParameters, EmbeddedPisoSolver, EmbeddedResult, EmbeddedSolverState};
|
||||||
pub use embedded_body::{
|
pub use embedded_body::{
|
||||||
polygon_interface_velocity, polygon_signed_distance, EmbeddedBody, EmbeddedMask, FaceKind,
|
EmbeddedBody, EmbeddedMask, FaceKind, SurfaceForce, SurfaceSample, polygon_interface_velocity,
|
||||||
SurfaceForce, SurfaceSample,
|
polygon_signed_distance,
|
||||||
};
|
};
|
||||||
pub use flow_field::FlowField;
|
pub use flow_field::FlowField;
|
||||||
pub use overset::{
|
pub use overset::{
|
||||||
CellClass, MomentumResidual, OverlapMap, OversetField, OversetParameters, OversetPisoSolver,
|
CellClass, MomentumResidual, OverlapMap, OversetField, OversetParameters, OversetPisoSolver,
|
||||||
OversetResult, OversetSolverState, ResidualBucket,
|
OversetResult, OversetSolverState, ResidualBucket, StepTimers,
|
||||||
};
|
};
|
||||||
pub use piso::{PisoParameters, PisoResult, PisoSolver};
|
pub use piso::{PisoParameters, PisoResult, PisoSolver};
|
||||||
#[cfg(feature = "cuda")]
|
#[cfg(feature = "cuda")]
|
||||||
|
|||||||
@@ -155,6 +155,35 @@ pub struct OversetSolverState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The composite solver.
|
/// The composite solver.
|
||||||
|
/// PERF-2 P0 (`docs/perf2_campaign.md`): where one composite step's wall
|
||||||
|
/// time goes, in nanoseconds, summed over the steps since construction.
|
||||||
|
/// Present only when `RTX_PROFILE` is set at construction; the phase
|
||||||
|
/// boundaries are the ones of [`OversetPisoSolver::advance`].
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct StepTimers {
|
||||||
|
/// The overlap rebuild + reclassification + fringe re-stamp (phase 1).
|
||||||
|
pub overlap_build_ns: u64,
|
||||||
|
/// The patch predictor (phase 1b: operators, predict, pressure matrix).
|
||||||
|
pub predictor_patch_ns: u64,
|
||||||
|
/// The background predictor (phase 2).
|
||||||
|
pub predictor_bg_ns: u64,
|
||||||
|
/// Patch pressure solves (BiCGSTAB), summed over rounds.
|
||||||
|
pub patch_solve_ns: u64,
|
||||||
|
/// Background pressure solves (rhs assembly + multigrid-PCG), summed.
|
||||||
|
pub bg_solve_ns: u64,
|
||||||
|
/// The rest of a round: gathers, scatters, mean removal, Anderson.
|
||||||
|
pub round_exchange_ns: u64,
|
||||||
|
/// The correctors' applications on both meshes.
|
||||||
|
pub apply_ns: u64,
|
||||||
|
/// The end-of-step exchange, defects and clocks (phase 4).
|
||||||
|
pub end_exchange_ns: u64,
|
||||||
|
/// The whole of `advance`.
|
||||||
|
pub advance_ns: u64,
|
||||||
|
/// Steps and Schwarz rounds counted.
|
||||||
|
pub steps: u64,
|
||||||
|
pub rounds: u64,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct OversetPisoSolver {
|
pub struct OversetPisoSolver {
|
||||||
background: EmbeddedPisoSolver,
|
background: EmbeddedPisoSolver,
|
||||||
patch: CurvilinearPisoSolver,
|
patch: CurvilinearPisoSolver,
|
||||||
@@ -162,6 +191,8 @@ pub struct OversetPisoSolver {
|
|||||||
params: OversetParameters,
|
params: OversetParameters,
|
||||||
grid: (usize, usize, f64, f64),
|
grid: (usize, usize, f64, f64),
|
||||||
pending: Option<PatchMesh>,
|
pending: Option<PatchMesh>,
|
||||||
|
/// PERF-2 P0 timers (`RTX_PROFILE`), or `None` — no clock is read then.
|
||||||
|
timers: Option<Box<StepTimers>>,
|
||||||
/// The first corrector's converged acceptor `p'` of the previous step:
|
/// The first corrector's converged acceptor `p'` of the previous step:
|
||||||
/// the temporal warm start (the correction is correlated step to step).
|
/// the temporal warm start (the correction is correlated step to step).
|
||||||
acceptor_warm: Vec<f64>,
|
acceptor_warm: Vec<f64>,
|
||||||
@@ -293,10 +324,32 @@ impl OversetPisoSolver {
|
|||||||
params,
|
params,
|
||||||
grid,
|
grid,
|
||||||
pending: None,
|
pending: None,
|
||||||
|
timers: std::env::var_os("RTX_PROFILE").map(|_| Box::default()),
|
||||||
acceptor_warm: Vec::new(),
|
acceptor_warm: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The step timers, when profiling (`RTX_PROFILE`).
|
||||||
|
pub fn timers(&self) -> Option<&StepTimers> {
|
||||||
|
self.timers.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Charge the time since `start` to `slot` and return a fresh start;
|
||||||
|
/// `None` in, `None` out when not profiling (no clock is read).
|
||||||
|
fn lap(
|
||||||
|
&mut self,
|
||||||
|
start: Option<std::time::Instant>,
|
||||||
|
slot: fn(&mut StepTimers) -> &mut u64,
|
||||||
|
) -> Option<std::time::Instant> {
|
||||||
|
match (start, self.timers.as_deref_mut()) {
|
||||||
|
(Some(t), Some(s)) => {
|
||||||
|
*slot(s) += t.elapsed().as_nanos() as u64;
|
||||||
|
Some(std::time::Instant::now())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The background solver.
|
/// The background solver.
|
||||||
pub fn background(&self) -> &EmbeddedPisoSolver {
|
pub fn background(&self) -> &EmbeddedPisoSolver {
|
||||||
&self.background
|
&self.background
|
||||||
@@ -512,6 +565,8 @@ impl OversetPisoSolver {
|
|||||||
/// Advance both meshes one step of `dt`.
|
/// Advance both meshes one step of `dt`.
|
||||||
pub async fn advance(&mut self, field: &mut OversetField, dt: f64) -> CfdResult<OversetResult> {
|
pub async fn advance(&mut self, field: &mut OversetField, dt: f64) -> CfdResult<OversetResult> {
|
||||||
let (nx, ny, dx, dy) = self.grid;
|
let (nx, ny, dx, dy) = self.grid;
|
||||||
|
let t_advance = self.timers.as_ref().map(|_| std::time::Instant::now());
|
||||||
|
let mut t_lap = t_advance;
|
||||||
|
|
||||||
// 1. If the patch moves, the overlap follows the NEXT mesh before any
|
// 1. If the patch moves, the overlap follows the NEXT mesh before any
|
||||||
// predictor runs: the background is reclassified (fresh cells
|
// predictor runs: the background is reclassified (fresh cells
|
||||||
@@ -663,14 +718,17 @@ impl OversetPisoSolver {
|
|||||||
self.overlap.stamp_fringe_cells(&mut field.background.p, &p);
|
self.overlap.stamp_fringe_cells(&mut field.background.p, &p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t_lap = self.lap(t_lap, |s| &mut s.overlap_build_ns);
|
||||||
// 1b. Patch predictor (swaps in the pending mesh).
|
// 1b. Patch predictor (swaps in the pending mesh).
|
||||||
let patch_start = self.patch.begin_step(&mut field.patch, dt)?;
|
let patch_start = self.patch.begin_step(&mut field.patch, dt)?;
|
||||||
|
t_lap = self.lap(t_lap, |s| &mut s.predictor_patch_ns);
|
||||||
|
|
||||||
// 2. Background predictor.
|
// 2. Background predictor.
|
||||||
let bg_start = self.background.begin_step(&mut field.background, dt)?;
|
let bg_start = self.background.begin_step(&mut field.background, dt)?;
|
||||||
if let Some(old) = &old_class {
|
if let Some(old) = &old_class {
|
||||||
self.trace_reclassification_source(field, old, dt);
|
self.trace_reclassification_source(field, old, dt);
|
||||||
}
|
}
|
||||||
|
t_lap = self.lap(t_lap, |s| &mut s.predictor_bg_ns);
|
||||||
|
|
||||||
// 3. Correctors: alternating Schwarz on the acceptor p' vector `a`
|
// 3. Correctors: alternating Schwarz on the acceptor p' vector `a`
|
||||||
// (patch solve with Dirichlet a → fringe p' → background solve →
|
// (patch solve with Dirichlet a → fringe p' → background solve →
|
||||||
@@ -721,6 +779,7 @@ impl OversetPisoSolver {
|
|||||||
}
|
}
|
||||||
None => patch_pc.iter_mut().for_each(|v| *v = 0.0),
|
None => patch_pc.iter_mut().for_each(|v| *v = 0.0),
|
||||||
}
|
}
|
||||||
|
t_lap = self.lap(t_lap, |s| &mut s.patch_solve_ns);
|
||||||
// Background with the fringe p' from the patch.
|
// Background with the fringe p' from the patch.
|
||||||
let fringe_vals = self.overlap.fringe_cell_values(&patch_pc);
|
let fringe_vals = self.overlap.fringe_cell_values(&patch_pc);
|
||||||
self.background
|
self.background
|
||||||
@@ -730,6 +789,7 @@ impl OversetPisoSolver {
|
|||||||
dt,
|
dt,
|
||||||
corrector == 0 || round > 0,
|
corrector == 0 || round > 0,
|
||||||
)?;
|
)?;
|
||||||
|
t_lap = self.lap(t_lap, |s| &mut s.bg_solve_ns);
|
||||||
// Pin the composite level: the coupled p' problem is pure
|
// Pin the composite level: the coupled p' problem is pure
|
||||||
// Neumann (walls everywhere), so its constant mode is
|
// Neumann (walls everywhere), so its constant mode is
|
||||||
// undamped by the rounds and the warm start hands each
|
// undamped by the rounds and the warm start hands each
|
||||||
@@ -744,6 +804,10 @@ impl OversetPisoSolver {
|
|||||||
step_scale = step_scale.max(g_max);
|
step_scale = step_scale.max(g_max);
|
||||||
let scale = step_scale.max(1e-300);
|
let scale = step_scale.max(1e-300);
|
||||||
let change = r.iter().fold(0.0_f64, |m, v| m.max(v.abs()));
|
let change = r.iter().fold(0.0_f64, |m, v| m.max(v.abs()));
|
||||||
|
t_lap = self.lap(t_lap, |s| &mut s.round_exchange_ns);
|
||||||
|
if let Some(s) = self.timers.as_deref_mut() {
|
||||||
|
s.rounds += 1;
|
||||||
|
}
|
||||||
if trace_rounds {
|
if trace_rounds {
|
||||||
println!(
|
println!(
|
||||||
" corrector {corrector} round {round}: |G(a) − a| {change:.3e} / step scale {scale:.3e} = {:.3e} (max|G(a)| {g_max:.3e})",
|
" corrector {corrector} round {round}: |G(a) − a| {change:.3e} / step scale {scale:.3e} = {:.3e} (max|G(a)| {g_max:.3e})",
|
||||||
@@ -785,6 +849,7 @@ impl OversetPisoSolver {
|
|||||||
} else {
|
} else {
|
||||||
next
|
next
|
||||||
};
|
};
|
||||||
|
t_lap = self.lap(t_lap, |s| &mut s.round_exchange_ns);
|
||||||
}
|
}
|
||||||
schwarz_converged &= done;
|
schwarz_converged &= done;
|
||||||
rounds.push(used);
|
rounds.push(used);
|
||||||
@@ -800,6 +865,7 @@ impl OversetPisoSolver {
|
|||||||
if corrector + 1 < self.params.corrector_steps.max(1) {
|
if corrector + 1 < self.params.corrector_steps.max(1) {
|
||||||
field.background.copy_to_starred();
|
field.background.copy_to_starred();
|
||||||
}
|
}
|
||||||
|
t_lap = self.lap(t_lap, |s| &mut s.apply_ns);
|
||||||
}
|
}
|
||||||
let patch_max_div = self.patch.max_divergence_pub(&field.patch.flux);
|
let patch_max_div = self.patch.max_divergence_pub(&field.patch.flux);
|
||||||
|
|
||||||
@@ -822,6 +888,11 @@ impl OversetPisoSolver {
|
|||||||
patch_iterations,
|
patch_iterations,
|
||||||
patch_converged,
|
patch_converged,
|
||||||
);
|
);
|
||||||
|
let _ = self.lap(t_lap, |s| &mut s.end_exchange_ns);
|
||||||
|
if let (Some(t0), Some(s)) = (t_advance, self.timers.as_deref_mut()) {
|
||||||
|
s.advance_ns += t0.elapsed().as_nanos() as u64;
|
||||||
|
s.steps += 1;
|
||||||
|
}
|
||||||
if (self.patch.time() - self.background.time()).abs()
|
if (self.patch.time() - self.background.time()).abs()
|
||||||
> 1e-12 * self.patch.time().abs().max(1.0)
|
> 1e-12 * self.patch.time().abs().max(1.0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
//! instead of using the prescribed boundary faces that exist there.
|
//! instead of using the prescribed boundary faces that exist there.
|
||||||
|
|
||||||
use super::poisson::{
|
use super::poisson::{
|
||||||
solve_multigrid_pcg, MgPrecision, MultigridParameters, PoissonProblem, PoissonSolverKind,
|
MgPrecision, MultigridParameters, PoissonProblem, PoissonSolverKind, solve_multigrid_pcg,
|
||||||
};
|
};
|
||||||
use super::{BoundaryConditions, FlowField, IncompressibleSolver, SolverResult};
|
use super::{BoundaryConditions, FlowField, IncompressibleSolver, SolverResult};
|
||||||
use crate::{CfdConfig, CfdResult};
|
use crate::{CfdConfig, CfdResult};
|
||||||
|
|||||||
@@ -310,6 +310,10 @@ pub struct PoissonSolution {
|
|||||||
pub residual: f64,
|
pub residual: f64,
|
||||||
/// `residual < tolerance` at exit.
|
/// `residual < tolerance` at exit.
|
||||||
pub converged: bool,
|
pub converged: bool,
|
||||||
|
/// Wall time of the setup (hierarchy, fine level, components) [ns].
|
||||||
|
pub setup_ns: u64,
|
||||||
|
/// Wall time of the CG iteration (including the V-cycles) [ns].
|
||||||
|
pub iterate_ns: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Which inner solver a projection uses for its pressure-correction system.
|
/// Which inner solver a projection uses for its pressure-correction system.
|
||||||
@@ -761,6 +765,7 @@ fn solve_pcg_with<T: MgScalar>(
|
|||||||
"invalid PoissonProblem: {:?}",
|
"invalid PoissonProblem: {:?}",
|
||||||
problem.validate()
|
problem.validate()
|
||||||
);
|
);
|
||||||
|
let t_entry = std::time::Instant::now();
|
||||||
|
|
||||||
let mut hier = Hierarchy::<T>::build(problem, params);
|
let mut hier = Hierarchy::<T>::build(problem, params);
|
||||||
let fine = Level::<f64>::new(problem.clone());
|
let fine = Level::<f64>::new(problem.clone());
|
||||||
@@ -771,6 +776,8 @@ fn solve_pcg_with<T: MgScalar>(
|
|||||||
iterations: 0,
|
iterations: 0,
|
||||||
residual: 0.0,
|
residual: 0.0,
|
||||||
converged: true,
|
converged: true,
|
||||||
|
setup_ns: t_entry.elapsed().as_nanos() as u64,
|
||||||
|
iterate_ns: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -818,6 +825,8 @@ fn solve_pcg_with<T: MgScalar>(
|
|||||||
|p: &[f64], r: &mut [f64], fine: &Level<f64>| -> f64 { fine.residual(&b, p, r) };
|
|p: &[f64], r: &mut [f64], fine: &Level<f64>| -> f64 { fine.residual(&b, p, r) };
|
||||||
|
|
||||||
let anchor = anchor.filter(|&a| a < n && fine.active[a]);
|
let anchor = anchor.filter(|&a| a < n && fine.active[a]);
|
||||||
|
let setup_ns = t_entry.elapsed().as_nanos() as u64;
|
||||||
|
let t_iter = std::time::Instant::now();
|
||||||
let finish = |p: &mut [f64], iterations: usize, residual: f64| {
|
let finish = |p: &mut [f64], iterations: usize, residual: f64| {
|
||||||
// Level of each singular component: the anchor's component is
|
// Level of each singular component: the anchor's component is
|
||||||
// shifted so p[anchor] == 0, every other singular component to mean
|
// shifted so p[anchor] == 0, every other singular component to mean
|
||||||
@@ -839,6 +848,8 @@ fn solve_pcg_with<T: MgScalar>(
|
|||||||
iterations,
|
iterations,
|
||||||
residual,
|
residual,
|
||||||
converged: residual < tolerance,
|
converged: residual < tolerance,
|
||||||
|
setup_ns,
|
||||||
|
iterate_ns: t_iter.elapsed().as_nanos() as u64,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -206,11 +206,7 @@ impl PolygonSdf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dist = dist2.sqrt();
|
let dist = dist2.sqrt();
|
||||||
if inside {
|
if inside { -dist } else { dist }
|
||||||
-dist
|
|
||||||
} else {
|
|
||||||
dist
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -218,6 +218,14 @@ pub struct OversetFluid {
|
|||||||
/// Patch regenerations and their wall time.
|
/// Patch regenerations and their wall time.
|
||||||
pub regen_count: Cell<usize>,
|
pub regen_count: Cell<usize>,
|
||||||
pub regen_seconds: Cell<f64>,
|
pub regen_seconds: Cell<f64>,
|
||||||
|
/// PERF-2 P0 wall-time buckets [s]: the composite step (`advance`), the
|
||||||
|
/// per-pass restore, the per-step snapshot, the load sampling and the
|
||||||
|
/// force measurement (`docs/perf2_campaign.md`).
|
||||||
|
pub t_advance: Cell<f64>,
|
||||||
|
pub t_restore: Cell<f64>,
|
||||||
|
pub t_snapshot: Cell<f64>,
|
||||||
|
pub t_sample: Cell<f64>,
|
||||||
|
pub t_force: Cell<f64>,
|
||||||
/// Background cells reclassified, summed over every fluid step.
|
/// Background cells reclassified, summed over every fluid step.
|
||||||
pub reclassified_total: Cell<usize>,
|
pub reclassified_total: Cell<usize>,
|
||||||
pub fresh_total: Cell<usize>,
|
pub fresh_total: Cell<usize>,
|
||||||
@@ -441,6 +449,11 @@ impl OversetFluid {
|
|||||||
sweeps,
|
sweeps,
|
||||||
regen_count: Cell::new(0),
|
regen_count: Cell::new(0),
|
||||||
regen_seconds: Cell::new(0.0),
|
regen_seconds: Cell::new(0.0),
|
||||||
|
t_advance: Cell::new(0.0),
|
||||||
|
t_restore: Cell::new(0.0),
|
||||||
|
t_snapshot: Cell::new(0.0),
|
||||||
|
t_sample: Cell::new(0.0),
|
||||||
|
t_force: Cell::new(0.0),
|
||||||
reclassified_total: Cell::new(0),
|
reclassified_total: Cell::new(0),
|
||||||
fresh_total: Cell::new(0),
|
fresh_total: Cell::new(0),
|
||||||
rounds_total: Cell::new(0),
|
rounds_total: Cell::new(0),
|
||||||
@@ -895,6 +908,7 @@ impl OversetFluid {
|
|||||||
|
|
||||||
/// One fluid step at the current wall.
|
/// One fluid step at the current wall.
|
||||||
pub fn step(&mut self) -> CfdResult<OversetResult> {
|
pub fn step(&mut self) -> CfdResult<OversetResult> {
|
||||||
|
let t0 = std::time::Instant::now();
|
||||||
let r = match futures::executor::block_on(
|
let r = match futures::executor::block_on(
|
||||||
self.solver.advance(&mut self.field, self.dt_fluid),
|
self.solver.advance(&mut self.field, self.dt_fluid),
|
||||||
) {
|
) {
|
||||||
@@ -919,6 +933,8 @@ impl OversetFluid {
|
|||||||
.set(self.rounds_total.get() + r.rounds.iter().sum::<usize>());
|
.set(self.rounds_total.get() + r.rounds.iter().sum::<usize>());
|
||||||
self.correctors_total
|
self.correctors_total
|
||||||
.set(self.correctors_total.get() + r.rounds.len());
|
.set(self.correctors_total.get() + r.rounds.len());
|
||||||
|
self.t_advance
|
||||||
|
.set(self.t_advance.get() + t0.elapsed().as_secs_f64());
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -987,11 +1003,14 @@ impl OversetFluid {
|
|||||||
|
|
||||||
/// Drag and lift on cylinder + flag from the patch's wall stress.
|
/// Drag and lift on cylinder + flag from the patch's wall stress.
|
||||||
pub fn measure_force(&self) -> (f64, f64) {
|
pub fn measure_force(&self) -> (f64, f64) {
|
||||||
|
let t0 = std::time::Instant::now();
|
||||||
let f = self
|
let f = self
|
||||||
.solver
|
.solver
|
||||||
.patch()
|
.patch()
|
||||||
.surface_force(&self.field.patch, PatchSide::Inner, self.solver.time())
|
.surface_force(&self.field.patch, PatchSide::Inner, self.solver.time())
|
||||||
.total();
|
.total();
|
||||||
|
self.t_force
|
||||||
|
.set(self.t_force.get() + t0.elapsed().as_secs_f64());
|
||||||
(f[0], f[1])
|
(f[0], f[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1000,6 +1019,14 @@ impl OversetFluid {
|
|||||||
/// Faces on the cylinder proper are skipped; the fillets' load goes
|
/// Faces on the cylinder proper are skipped; the fillets' load goes
|
||||||
/// to the nearest (clamped) root nodes.
|
/// to the nearest (clamped) root nodes.
|
||||||
pub fn sample_load(&self, d: &[f64]) -> (Vec<(NodeId, Vector3<f64>)>, f64, usize) {
|
pub fn sample_load(&self, d: &[f64]) -> (Vec<(NodeId, Vector3<f64>)>, f64, usize) {
|
||||||
|
let t0 = std::time::Instant::now();
|
||||||
|
let out = self.sample_load_inner(d);
|
||||||
|
self.t_sample
|
||||||
|
.set(self.t_sample.get() + t0.elapsed().as_secs_f64());
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_load_inner(&self, d: &[f64]) -> (Vec<(NodeId, Vector3<f64>)>, f64, usize) {
|
||||||
let mut faces = Vec::new();
|
let mut faces = Vec::new();
|
||||||
let mut tractions: Vec<Vector3<f64>> = Vec::new();
|
let mut tractions: Vec<Vector3<f64>> = Vec::new();
|
||||||
for (centre, normal, len, traction) in self.solver.patch().wall_tractions(
|
for (centre, normal, len, traction) in self.solver.patch().wall_tractions(
|
||||||
@@ -1117,12 +1144,63 @@ impl OversetFluid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn snapshot(&self) -> (OversetSolverState, OversetField) {
|
pub fn snapshot(&self) -> (OversetSolverState, OversetField) {
|
||||||
(self.solver.snapshot(), self.field.clone())
|
let t0 = std::time::Instant::now();
|
||||||
|
let out = (self.solver.snapshot(), self.field.clone());
|
||||||
|
self.t_snapshot
|
||||||
|
.set(self.t_snapshot.get() + t0.elapsed().as_secs_f64());
|
||||||
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore(&mut self, saved: &(OversetSolverState, OversetField)) {
|
pub fn restore(&mut self, saved: &(OversetSolverState, OversetField)) {
|
||||||
|
let t0 = std::time::Instant::now();
|
||||||
self.solver.restore(&saved.0);
|
self.solver.restore(&saved.0);
|
||||||
self.field = saved.1.clone();
|
self.field = saved.1.clone();
|
||||||
|
self.t_restore
|
||||||
|
.set(self.t_restore.get() + t0.elapsed().as_secs_f64());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PERF-2 P0: the solver's own split of `advance` (when `RTX_PROFILE`
|
||||||
|
/// is set) as one printable line, with the background Poisson's
|
||||||
|
/// setup / iterate split.
|
||||||
|
pub fn profile_line(&self) -> Option<String> {
|
||||||
|
let t = self.solver.timers()?;
|
||||||
|
let (ps, pi, pc) = self.solver.background().poisson_profile();
|
||||||
|
let s = |ns: u64| ns as f64 * 1e-9;
|
||||||
|
let adv = s(t.advance_ns).max(1e-300);
|
||||||
|
let pct = |ns: u64| 100.0 * s(ns) / adv;
|
||||||
|
Some(format!(
|
||||||
|
" advance split over {} steps ({} rounds), {:.0} s: overlap build {:.0} s ({:.1}%), predictor bg {:.0} s ({:.1}%), predictor patch {:.0} s ({:.1}%), bg Poisson {:.0} s ({:.1}%) [setup {:.0} s, iterate {:.0} s, {} solves], patch BiCGSTAB {:.0} s ({:.1}%), round exchange {:.0} s ({:.1}%), apply {:.0} s ({:.1}%), end exchange {:.0} s ({:.1}%), other {:.0} s",
|
||||||
|
t.steps,
|
||||||
|
t.rounds,
|
||||||
|
adv,
|
||||||
|
s(t.overlap_build_ns),
|
||||||
|
pct(t.overlap_build_ns),
|
||||||
|
s(t.predictor_bg_ns),
|
||||||
|
pct(t.predictor_bg_ns),
|
||||||
|
s(t.predictor_patch_ns),
|
||||||
|
pct(t.predictor_patch_ns),
|
||||||
|
s(t.bg_solve_ns),
|
||||||
|
pct(t.bg_solve_ns),
|
||||||
|
s(ps),
|
||||||
|
s(pi),
|
||||||
|
pc,
|
||||||
|
s(t.patch_solve_ns),
|
||||||
|
pct(t.patch_solve_ns),
|
||||||
|
s(t.round_exchange_ns),
|
||||||
|
pct(t.round_exchange_ns),
|
||||||
|
s(t.apply_ns),
|
||||||
|
pct(t.apply_ns),
|
||||||
|
s(t.end_exchange_ns),
|
||||||
|
pct(t.end_exchange_ns),
|
||||||
|
adv - s(t.overlap_build_ns
|
||||||
|
+ t.predictor_bg_ns
|
||||||
|
+ t.predictor_patch_ns
|
||||||
|
+ t.bg_solve_ns
|
||||||
|
+ t.patch_solve_ns
|
||||||
|
+ t.round_exchange_ns
|
||||||
|
+ t.apply_ns
|
||||||
|
+ t.end_exchange_ns),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn time(&self) -> f64 {
|
pub fn time(&self) -> f64 {
|
||||||
|
|||||||
@@ -377,6 +377,8 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
|
|||||||
f
|
f
|
||||||
});
|
});
|
||||||
let (t_fluid, t_structure) = (std::cell::Cell::new(0.0_f64), std::cell::Cell::new(0.0_f64));
|
let (t_fluid, t_structure) = (std::cell::Cell::new(0.0_f64), std::cell::Cell::new(0.0_f64));
|
||||||
|
// PERF-2 P0: the FEA predictor step (outside both buckets before).
|
||||||
|
let t_predictor = std::cell::Cell::new(0.0_f64);
|
||||||
let prev_area = std::cell::Cell::new(fluid.borrow().shared.read().unwrap().area());
|
let prev_area = std::cell::Cell::new(fluid.borrow().shared.read().unwrap().area());
|
||||||
let save_from: f64 = std::env::var("RTX_FSI2O_SAVE_FROM")
|
let save_from: f64 = std::env::var("RTX_FSI2O_SAVE_FROM")
|
||||||
.ok()
|
.ok()
|
||||||
@@ -395,9 +397,11 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
|
|||||||
let v = extract_velocity(&flag_state);
|
let v = extract_velocity(&flag_state);
|
||||||
d_n.iter().zip(&v).map(|(d, v)| d + dt * v).collect()
|
d_n.iter().zip(&v).map(|(d, v)| d + dt * v).collect()
|
||||||
} else {
|
} else {
|
||||||
|
let ps = std::time::Instant::now();
|
||||||
flag.borrow_mut()
|
flag.borrow_mut()
|
||||||
.set_nodal_forces(&with_fict(&committed_nodal, &extract_accel(&flag_state)));
|
.set_nodal_forces(&with_fict(&committed_nodal, &extract_accel(&flag_state)));
|
||||||
let (predicted, _) = flag.borrow_mut().step(&flag_state).unwrap();
|
let (predicted, _) = flag.borrow_mut().step(&flag_state).unwrap();
|
||||||
|
t_predictor.set(t_predictor.get() + ps.elapsed().as_secs_f64());
|
||||||
extract(&predicted)
|
extract(&predicted)
|
||||||
};
|
};
|
||||||
if robin_alpha > 0.0 {
|
if robin_alpha > 0.0 {
|
||||||
@@ -582,7 +586,7 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
|
|||||||
total_subiterations as f64 / (step + 1) as f64,
|
total_subiterations as f64 / (step + 1) as f64,
|
||||||
fl.rounds_total.get() as f64 / fl.correctors_total.get().max(1) as f64,
|
fl.rounds_total.get() as f64 / fl.correctors_total.get().max(1) as f64,
|
||||||
fl.reclassified_total.get() as f64
|
fl.reclassified_total.get() as f64
|
||||||
/ (rigid_steps + (step + 1) * cfg.subcycle * 4).max(1) as f64,
|
/ (rigid_steps + (step + 1) * cfg.subcycle).max(1) as f64,
|
||||||
fl.regen_seconds.get(),
|
fl.regen_seconds.get(),
|
||||||
t_fluid.get(),
|
t_fluid.get(),
|
||||||
phase_start.elapsed().as_secs_f64()
|
phase_start.elapsed().as_secs_f64()
|
||||||
@@ -590,6 +594,37 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let fl = fluid.borrow();
|
let fl = fluid.borrow();
|
||||||
|
// PERF-2 P0: the wall split of the coupled phase (every bucket the
|
||||||
|
// harness can see; `other` is what none of them caught).
|
||||||
|
{
|
||||||
|
let wall = phase_start.elapsed().as_secs_f64().max(1e-300);
|
||||||
|
let pct = |x: f64| 100.0 * x / wall;
|
||||||
|
let (adv, res, snap, samp, force, regen) = (
|
||||||
|
fl.t_advance.get(),
|
||||||
|
fl.t_restore.get(),
|
||||||
|
fl.t_snapshot.get(),
|
||||||
|
fl.t_sample.get(),
|
||||||
|
fl.t_force.get(),
|
||||||
|
fl.regen_seconds.get(),
|
||||||
|
);
|
||||||
|
let (st, pr) = (t_structure.get(), t_predictor.get());
|
||||||
|
let other = wall - (adv + res + snap + samp + force + regen + st + pr);
|
||||||
|
println!(
|
||||||
|
" wall split over the coupled phase ({wall:.0} s): fluid advance {adv:.0} s ({:.1}%), patch regeneration {regen:.0} s ({:.1}%), restore {res:.0} s ({:.1}%), snapshot {snap:.0} s ({:.1}%), load sampling {samp:.0} s ({:.1}%), force {force:.0} s ({:.1}%), structure {st:.0} s ({:.1}%), FEA predictor {pr:.0} s ({:.1}%), other {other:.0} s ({:.1}%)",
|
||||||
|
pct(adv),
|
||||||
|
pct(regen),
|
||||||
|
pct(res),
|
||||||
|
pct(snap),
|
||||||
|
pct(samp),
|
||||||
|
pct(force),
|
||||||
|
pct(st),
|
||||||
|
pct(pr),
|
||||||
|
pct(other)
|
||||||
|
);
|
||||||
|
if let Some(line) = fl.profile_line() {
|
||||||
|
println!("{line}");
|
||||||
|
}
|
||||||
|
}
|
||||||
let final_state_finite = flag_state.displacement.iter().all(|v| v.is_finite());
|
let final_state_finite = flag_state.displacement.iter().all(|v| v.is_finite());
|
||||||
let steps_done = times.len();
|
let steps_done = times.len();
|
||||||
OversetMarchResult {
|
OversetMarchResult {
|
||||||
|
|||||||
Reference in New Issue
Block a user