Repository navigation
OptimizationMOI: const f in MOIOptimizationNLPEvaluator to avoid large field copies - #1404
Conversation
MOIOptimizationNLPEvaluator stores the instantiated OptimizationFunction inline; at n=10 sizeof(f) is ~39 KB, so every evaluator.f load from the mutable struct copied the whole field on Julia 1.12. Declaring const f::F (Julia >= 1.8; package compat is 1.10) lets the compiler elide those copies. Nothing reassigns evaluator.f. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Cursor Agent <noreply@cursor.com> Agent-Harness: Cursor Agent 2026.10.01-14929f9 Agent-Model: unknown (Cursor auto) Agent-Session: local session, transcript /home/crackauc/sandbox/goals/performance/jobs/opt/opt-moi/log.txt on amdci2
Independent review (Devin CLI 3000.11.3, model fusion-claude-opus-5-5-high-sidekick-swe-2-medium): MERGE, risk low. Full reviewVERDICT: MERGE PR: #1404. The diff is one token: Blocking findingsNone. Non-blocking findings
What I ranAll runs were on this Mac (M2 Max) with
What I did not verify
🤖 Posted by an AI agent — harness: Devin CLI 3000.11.3 (review), Claude Code 2.1.285 (posting) · model: fusion-claude-opus-5-5-high-sidekick-swe-2-medium |
Summary
MOIOptimizationNLPEvaluatoris a mutable struct that stores the instantiatedOptimizationFunctioninline asf::F. Atn=10with AutoForwardDiff + Hessian,sizeof(ev.f)is ~39 KB (DI Hessian preps), so everyevaluator.fload from the mutable container copied the whole field on Julia 1.12. Declaringconst f::F(supported since Julia 1.8; OptimizationMOI compat isjulia = "1.10") lets the compiler elide those copies. Grep confirms nothing reassignsevaluator.f.Before / after (Julia 1.12.7,
JULIA_NUM_THREADS=1,taskset -c 95,@benchmarkminima)Rosenbrock + 3 constraints, AutoForwardDiff, Ipopt NLP callbacks. Identical callback values and Ipopt solutions before/after (
n=2: 25 iters →[0.9999785, 0.9999569];n=10: 12 iters → sameu/ objective).eval_objective_gradienteval_constraint_jacobianeval_hessian_lagrangiann=2(smallf): gradient 65→38 ns, jacobian 115→95 ns, hessian 439→408 ns.Julia 1.10.12: callback values and Ipopt solves match the 1.12 results byte-for-byte on
u/obj/iters. Absolute MOI-callback times on 1.10 were ~µs-scale both before and after in this env (other overhead appears to dominate; const still compiles and is correct).Test group tails
OPTIMIZATION_TEST_GROUP=Core(Julia 1.12,lib/OptimizationMOI):Core | 40 Pass, 2 Broken, 42 Total 4m43.3s; Testing OptimizationMOI tests passedOPTIMIZATION_TEST_GROUP=QA(Julia 1.12):Quality Assurance | 20 Pass, 1 Broken, 21 Total 2m30.7s; Testing OptimizationMOI tests passedlib/OptimizationMOI/src/nlp.jl: exit 0 (diff is the singleconsttoken; file was already Runic-clean on master)Not verified
GROUP=All/ other sublibrariesreinit_cache, matrix buffers)constwould help furthern(callback microbenchmarks only; solve time was already dominated by Ipopt itself)Reviewer pushback
const fthe right layer, or should hot paths usegetfield(evaluator, :f)and leave the field mutable for theoretical reassignment throughMOIOptimizationNLPCache’ssetproperty!?getpropertyoverride.Please ignore until reviewed by @ChrisRackauckas.
Behavior note: because
MOIOptimizationNLPCache'ssetproperty!forwards to the evaluator,cache.f = gnow throws (const field). Nothing in the repo assigns it;reinit!only touchesreinit_cache.Risk assessment
fwas never reassignedf; n=10 eval_objective_gradient 1161 -> 226 ns, constraint_jacobian 1249 -> 292 ns, hessian_lagrangian 6214 -> 5522 ns; Ipopt n=2 and n=10 results identical; OptimizationMOI Core passes.🤖 Generated with Cursor Agent 2026.10.01-14929f9 (model: unknown (Cursor auto)), transcript /home/crackauc/sandbox/goals/performance/jobs/opt/opt-moi/log.txt on amdci2; orchestrated by Claude Code (claude-opus-5-5[1m]) https://claude.ai/code/session_01LPHREnnonfLg1VcE1EJovv
Made with Cursor
Independent review: Devin Fusion (fusion-claude-opus-5-5-high-sidekick-swe-2-medium) rated it low, verdict MERGE: #1404 (comment)