Skip to content

materials: build the deviatoric projector once, not per call (#77) - #80

Merged
petlenz merged 1 commit into
mainfrom
perf/cached-iidev
Sep 20, 2026
Merged

petlenz merged 1 commit into
mainfrom
perf/cached-iidev

Conversation

@petlenz

@petlenz petlenz commented Sep 20, 2026

Copy link
Copy Markdown
Member

Closes #77.

Both generic yield-function policies constructed the rank-4 IIdev inside flow_normal_stress_derivative(), which runs once per plastic step:

const tensor4 IIdev{plasticity_detail::make_IIdev<T, Dim>()};

It depends on nothing but T and Dim.

Measured

-O2 -DNDEBUG, paired/interleaved timing (from the performance review on #77):

fresh cached
make_IIdev<double,3>() 149 ns 2.6 ns
plastic step before after
drucker_prager_plasticity 700 ns 548 ns -22%
j2_rk_plasticity (implicit_euler) 971 ns 759 ns -22%
j2_rk_plasticity (sdirk3) 1119 ns 953 ns -15%

j2_plasticity had always cached it in a member, which is most of why its plastic step was 3-6x cheaper than the two materials that go through the policies. This brings them to the same standard.

Why a function-local static

The policies are value types that get copied around, so a member would be rebuilt per copy. This way one instance is shared by every material at that (T, Dim). Initialization is thread safe, which matters because the UMAT layer runs one context per thread; after the first call only the guard check remains, and that is inside the 2.6 ns above.

Correctness

Stress and tangent are unchanged -- the existing J2, Drucker-Prager and RK value assertions all still pass. 311/311.

Both generic yield-function policies constructed the rank-4 IIdev inside
flow_normal_stress_derivative(), which runs once per plastic step:

  const tensor4 IIdev{plasticity_detail::make_IIdev<T, Dim>()};

IIdev depends on nothing but T and Dim. Measured at -O2 -DNDEBUG, building
it costs 149 ns against 2.6 ns to read a cached one, and it was worth
15-22% of a plastic step:

  drucker_prager_plasticity          700 ns -> 548 ns   (-22%)
  j2_rk_plasticity(implicit_euler)   971 ns -> 759 ns   (-22%)
  j2_rk_plasticity(sdirk3)          1119 ns -> 953 ns   (-15%)

j2_plasticity had always cached it in a member, which is most of why its
plastic step was 3-6x cheaper than the two materials that go through the
policies. This brings them to the same standard.

A function-local static rather than a member: the policies are value types
that get copied around, so one instance is shared by every material at
that (T, Dim). Initialization is thread safe, which matters because the
UMAT layer runs one context per thread; after the first call only the
guard check remains, and that is inside the 2.6 ns above.

Stress and tangent are unchanged -- the existing J2, Drucker-Prager and
RK value assertions all still pass.
@petlenz
petlenz merged commit d70cad0 into main Sep 20, 2026
1 check passed
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.

Yield-function policies rebuild the rank-4 IIdev on every call: 15-22% of a Drucker-Prager / RK plastic step

1 participant