Skip to content

perf: Remove redundant evaluations in unconstrained solver loops - #173

Closed
PatWie wants to merge 1 commit into
mainfrom
static-alloc
Closed

perf: Remove redundant evaluations in unconstrained solver loops#173
PatWie wants to merge 1 commit into
mainfrom
static-alloc

Conversation

@PatWie

@PatWie PatWie commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Progress::Update evaluated a full extra Hessian and computed a dense inverse every iteration of every second-order solve, purely to fill the condition-number diagnostic whose stopping threshold defaults to 0 (disabled). The diagnostic is now computed only when its stopping test is enabled and reports NaN otherwise (which the progress printer already renders as N/A).

GradientDescent and ConjugatedGradientDescent re-evaluated the gradient that the incoming FunctionState already carries; GradientDescent additionally returned a value-only state, forcing one post-step re-evaluation in the outer loop -- it now uses the state-returning More-Thuente overload. Armijo gains SearchWithCachedStart variants (both orders) so Newton and CG stop re-evaluating the starting point the caller just evaluated; Newton previously paid a value+gradient+Hessian triple per line search for this. Trial points in Armijo reuse one buffer across backtracks.

Measured on 2D Rosenbrock (g++ -O2, median of 201 solves), with iteration counts and final objectives bit-identical: CG 31.2 -> 12.5 us (2.5x), Newton 14.3 -> 7.0 us (2.0x), trust-region Newton 17.3 -> 10.5 us (1.65x), gradient descent 199.5 -> 153.5 us (1.3x). LBFGS and BFGS were already evaluation-clean and are unchanged.

Progress::Update evaluated a full extra Hessian and computed a dense
inverse every iteration of every second-order solve, purely to fill
the condition-number diagnostic whose stopping threshold defaults to
0 (disabled).  The diagnostic is now computed only when its stopping
test is enabled and reports NaN otherwise (which the progress printer
already renders as N/A).

GradientDescent and ConjugatedGradientDescent re-evaluated the
gradient that the incoming FunctionState already carries;
GradientDescent additionally returned a value-only state, forcing one
post-step re-evaluation in the outer loop -- it now uses the
state-returning More-Thuente overload.  Armijo gains
SearchWithCachedStart variants (both orders) so Newton and CG stop
re-evaluating the starting point the caller just evaluated; Newton
previously paid a value+gradient+Hessian triple per line search for
this.  Trial points in Armijo reuse one buffer across backtracks.

Measured on 2D Rosenbrock (g++ -O2, median of 201 solves), with
iteration counts and final objectives bit-identical: CG 31.2 -> 12.5
us (2.5x), Newton 14.3 -> 7.0 us (2.0x), trust-region Newton 17.3 ->
10.5 us (1.65x), gradient descent 199.5 -> 153.5 us (1.3x).  LBFGS
and BFGS were already evaluation-clean and are unchanged.

Also reflows one over-length line that the upstream rho-precompute
commit left unformatted in lbfgs.h; the CI format check scans the
whole tree, so the violation failed this PR despite originating on
main.
@PatWie

PatWie commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

Landed on main as e77bd94 (patch-identical to the PR head 4ff92ca); closing.

@PatWie PatWie closed this Jul 21, 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