Skip to content

Moved mat_mul operations outside inner loops and - #607

Closed
dseyler wants to merge 8 commits into
SimVascular:mainfrom
dseyler:perf/eigen-fixed-size-visc-stress
Closed

Moved mat_mul operations outside inner loops and #607
dseyler wants to merge 8 commits into
SimVascular:mainfrom
dseyler:perf/eigen-fixed-size-visc-stress

Conversation

@dseyler

@dseyler dseyler commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Current situation

After profiling a 500k element structural mechanics simulation, I noticed several mat_mul function calls that were unnecessarily repeated within loops, as discussed in issue 602.

These occurred at:

  • mat_models.cpp lines 1695-1696
  • sv_struct.cpp line 750
  • ustruct.cpp line 144

and have now been moved outside their loops.

Additionally, while investigating, it was noticed that many of these mat_mul operations were of fixed-size arrays with sizes known at compilation. These can be optimized with fixed-size Eigen maps, which run 3-4x faster than the generic mat_mul operation.

New fixed-size functions were defined in mat_mul.cpp:

  • mat_mul_fixed()
  • mat_mul_fixed_rows()

These functions are then dispatched within mat_mul() so developers do not need to distinguish between these differences.

///
/// Used when sizes are known at compile time.
template <int M, int K, int N>
inline void mat_mul_fixed(const Array<double>& A, const Array<double>& B, Array<double>& C)
{
  Eigen::Map<const Eigen::Matrix<double, M, K>> a(A.data());
  Eigen::Map<const Eigen::Matrix<double, K, N>> b(B.data());
  Eigen::Map<Eigen::Matrix<double, M, N>>       c(C.data());

  c.noalias() = a * b;
}

And in mat_mul:

{ 
  // Fixed-shape fast paths for the products that dominate the element loops.
  //
  //   3x3 * 3x3     F*S in struct_3d; vx*Fi, ddev*Fit, Fi*ddev_Fit and the
  //                 potential-viscosity products in mat_models; F^T*F in cep
  //   3x3 * 3xeNoN  ddev*Nx_Fi and vx_Fi*Nx_Fi in the viscous tangent
  //   6x6 * 6x3     the material stiffness product D*B in struct_3d/ustruct_3d_m
  if (A.nrows() == 3 && A.ncols() == 3 && B.nrows() == 3 &&
      result.nrows() == 3 && result.ncols() == B.ncols()) {
    if (B.ncols() == 3) {
      mat_mul_fixed<3, 3, 3>(A, B, result);
    } else {
      mat_mul_fixed_rows<3, 3>(A, B, result);
    }
    return;
  }

In total, these changes decreased runtime by ~21%.

hoist_branch

I suspect this number could increase much more if we correctly implement eigen arrays throughout sv_struct.cpp and mat_models.cpp as many arrays are of size nsd or enon, which have a discrete number of values and can be optimized similarly. I tested this on the viscosity models alone and found another 10% improvement (can open an issue about this, but the code is too messy for this PR)

Release Notes

  • Moved mat_mul outside loops in several locations
  • Implemented fixed-shape mat_mul functions
  • mat_mul directs arrays with know sizes at compilation down these paths

Code of Conduct & Contributing Guidelines

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@dseyler

dseyler commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Sorry, opened with wrong branch. Reopening with the correct one!

@dseyler dseyler closed this Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant