Skip to content

Commit 050276c

Browse files
committed
do
1 parent 859c634 commit 050276c

File tree

2 files changed

+234
-15
lines changed

2 files changed

+234
-15
lines changed

src/julia_interface.jl

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,27 @@ function cons_coord!(
225225
@lencheck nlp.meta.ncon c
226226
@lencheck nlp.meta.nnzj rows cols vals
227227

228-
# Use preallocated vectors instead of allocating new ones (Issue #392)
229-
cons_coord!(nlp, Vector{T}(x), nlp.cons_vals, nlp.jac_coord_rows, nlp.jac_coord_cols, nlp.jac_coord_vals)
228+
# Resize workspace vectors on demand if needed (Issue #392 - double buffering)
229+
nnzj = nlp.meta.nnzj
230+
if length(nlp.jac_coord_rows) < nnzj
231+
resize!(nlp.jac_coord_rows, nnzj)
232+
resize!(nlp.jac_coord_cols, nnzj)
233+
resize!(nlp.jac_coord_vals, nnzj)
234+
end
235+
if length(nlp.cons_vals) < nlp.meta.ncon
236+
resize!(nlp.cons_vals, nlp.meta.ncon)
237+
end
238+
239+
# Use preallocated vectors instead of allocating new ones
240+
cons_coord!(nlp, Vector{T}(x), view(nlp.cons_vals, 1:nlp.meta.ncon),
241+
view(nlp.jac_coord_rows, 1:nnzj), view(nlp.jac_coord_cols, 1:nnzj),
242+
view(nlp.jac_coord_vals, 1:nnzj))
230243

231244
# Copy results to output vectors
232-
rows .= nlp.jac_coord_rows
233-
cols .= nlp.jac_coord_cols
234-
vals .= nlp.jac_coord_vals
235-
c .= nlp.cons_vals
245+
rows .= view(nlp.jac_coord_rows, 1:nnzj)
246+
cols .= view(nlp.jac_coord_cols, 1:nnzj)
247+
vals .= view(nlp.jac_coord_vals, 1:nnzj)
248+
c .= view(nlp.cons_vals, 1:nlp.meta.ncon)
236249
return c, rows, cols, vals
237250
end
238251

@@ -255,14 +268,27 @@ Usage:
255268
function cons_coord(nlp::CUTEstModel{T}, x::StrideOneVector{T}) where {T}
256269
@lencheck nlp.meta.nvar x
257270

258-
# Use preallocated vectors to avoid allocations (Issue #392)
259-
cons_coord!(nlp, x, nlp.cons_vals, nlp.jac_coord_rows, nlp.jac_coord_cols, nlp.jac_coord_vals)
271+
# Resize workspace vectors on demand if needed (Issue #392 - double buffering)
272+
nnzj = nlp.meta.nnzj
273+
if length(nlp.jac_coord_rows) < nnzj
274+
resize!(nlp.jac_coord_rows, nnzj)
275+
resize!(nlp.jac_coord_cols, nnzj)
276+
resize!(nlp.jac_coord_vals, nnzj)
277+
end
278+
if length(nlp.cons_vals) < nlp.meta.ncon
279+
resize!(nlp.cons_vals, nlp.meta.ncon)
280+
end
281+
282+
# Use preallocated vectors to avoid allocations
283+
cons_coord!(nlp, x, view(nlp.cons_vals, 1:nlp.meta.ncon),
284+
view(nlp.jac_coord_rows, 1:nnzj), view(nlp.jac_coord_cols, 1:nnzj),
285+
view(nlp.jac_coord_vals, 1:nnzj))
260286

261287
# Return copies of the results to maintain API compatibility
262-
c = copy(nlp.cons_vals)
263-
rows = copy(nlp.jac_coord_rows)
264-
cols = copy(nlp.jac_coord_cols)
265-
vals = copy(nlp.jac_coord_vals)
288+
c = copy(view(nlp.cons_vals, 1:nlp.meta.ncon))
289+
rows = copy(view(nlp.jac_coord_rows, 1:nnzj))
290+
cols = copy(view(nlp.jac_coord_cols, 1:nnzj))
291+
vals = copy(view(nlp.jac_coord_vals, 1:nnzj))
266292

267293
return c, rows, cols, vals
268294
end
@@ -682,9 +708,15 @@ function NLPModels.hess_coord!(
682708
@lencheck nlp.meta.ncon y
683709
@lencheck nlp.meta.nnzh vals
684710

685-
# Use preallocated vector instead of allocating (Issue #392)
686-
NLPModels.hess_coord!(nlp, Vector{T}(x), convert(Vector{T}, y), nlp.hess_coord_vals, obj_weight = obj_weight)
687-
vals .= nlp.hess_coord_vals
711+
# Resize workspace vector on demand if needed (Issue #392 - double buffering)
712+
if length(nlp.hess_coord_vals) < nlp.meta.nnzh
713+
resize!(nlp.hess_coord_vals, nlp.meta.nnzh)
714+
end
715+
716+
# Use preallocated vector instead of allocating
717+
NLPModels.hess_coord!(nlp, Vector{T}(x), convert(Vector{T}, y),
718+
view(nlp.hess_coord_vals, 1:nlp.meta.nnzh), obj_weight = obj_weight)
719+
vals .= view(nlp.hess_coord_vals, 1:nlp.meta.nnzh)
688720
return vals
689721
end
690722

test/test_double_buffering.jl

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
using Test
2+
using CUTEst
3+
using NLPModels
4+
5+
@testset "Double Buffering Tests" begin
6+
# Test with a simple problem that has constraints and Jacobian
7+
nlp = CUTEstModel("HS9")
8+
9+
try
10+
# Test initial state - workspace vectors should start empty or be resized on demand
11+
x = [1.0, 1.0]
12+
13+
# Test cons_coord! function
14+
c = zeros(nlp.meta.ncon)
15+
rows = zeros(Int, nlp.meta.nnzj)
16+
cols = zeros(Int, nlp.meta.nnzj)
17+
vals = zeros(nlp.meta.nnzj)
18+
19+
# This should trigger workspace allocation/resizing
20+
@test_nowarn cons_coord!(nlp, x, c, rows, cols, vals)
21+
22+
# Verify results are reasonable
23+
@test length(c) == nlp.meta.ncon
24+
@test length(rows) == nlp.meta.nnzj
25+
@test length(cols) == nlp.meta.nnzj
26+
@test length(vals) == nlp.meta.nnzj
27+
28+
# Test cons_coord function (should reuse workspace)
29+
c2, rows2, cols2, vals2 = cons_coord(nlp, x)
30+
31+
@test c c2
32+
@test rows == rows2
33+
@test cols == cols2
34+
@test vals vals2
35+
36+
# Test with different vector types
37+
x_view = view([1.0, 1.0, 0.0], 1:2)
38+
c3, rows3, cols3, vals3 = cons_coord(nlp, x_view)
39+
40+
@test c2 c3
41+
@test rows2 == rows3
42+
@test cols2 == cols3
43+
@test vals2 vals3
44+
45+
# Test hess_coord! function if problem has Hessian
46+
if nlp.meta.nnzh > 0
47+
hvals = zeros(nlp.meta.nnzh)
48+
y = zeros(nlp.meta.ncon)
49+
50+
@test_nowarn NLPModels.hess_coord!(nlp, x, y, hvals)
51+
@test length(hvals) == nlp.meta.nnzh
52+
53+
# Test with different y vector - use larger multipliers to ensure difference
54+
y2 = 10.0 * ones(nlp.meta.ncon)
55+
hvals2 = zeros(nlp.meta.nnzh)
56+
@test_nowarn NLPModels.hess_coord!(nlp, x, y2, hvals2)
57+
58+
# Test with zero objective weight to isolate constraint Hessian
59+
hvals3 = zeros(nlp.meta.nnzh)
60+
@test_nowarn NLPModels.hess_coord!(nlp, x, y2, hvals3; obj_weight = 0.0)
61+
62+
# At least one of these should be different unless all constraint Hessians are zero
63+
different_found = !(hvals hvals2) || !(hvals hvals3) || !(hvals2 hvals3)
64+
@test different_found || all(abs.(hvals3) .< 1e-12) # Either different or constraint Hessians are essentially zero
65+
end
66+
67+
finally
68+
finalize(nlp)
69+
end
70+
end
71+
72+
@testset "Memory Efficiency Tests" begin
73+
# Test that workspace vectors are only allocated when needed
74+
nlp = CUTEstModel("HS9")
75+
76+
try
77+
x = [1.0, 1.0]
78+
79+
# First, check if workspace vectors exist
80+
has_jac_workspace = hasfield(typeof(nlp), :jac_coord_rows) &&
81+
hasfield(typeof(nlp), :jac_coord_cols) &&
82+
hasfield(typeof(nlp), :jac_coord_vals) &&
83+
hasfield(typeof(nlp), :cons_vals)
84+
85+
if has_jac_workspace
86+
println("Model has jacobian workspace vectors")
87+
else
88+
println("Model does not have jacobian workspace vectors - workspace not implemented yet")
89+
end
90+
91+
# Check that multiple calls don't cause excessive allocations
92+
initial_allocations = @allocated begin
93+
c, rows, cols, vals = cons_coord(nlp, x)
94+
end
95+
96+
# Second call should have minimal allocations due to workspace reuse (if implemented)
97+
second_allocations = @allocated begin
98+
c2, rows2, cols2, vals2 = cons_coord(nlp, x)
99+
end
100+
101+
# Third call to verify consistency
102+
third_allocations = @allocated begin
103+
c3, rows3, cols3, vals3 = cons_coord(nlp, x)
104+
end
105+
106+
println("Initial allocations: $initial_allocations bytes")
107+
println("Second allocations: $second_allocations bytes")
108+
println("Third allocations: $third_allocations bytes")
109+
110+
if has_jac_workspace
111+
# The current implementation might still allocate for return values
112+
# Test that allocations are reasonable and consistent
113+
@test second_allocations <= initial_allocations # Should not increase
114+
@test third_allocations == second_allocations # Should be consistent
115+
116+
# Test that workspace reuse is working by checking if internal calls allocate less
117+
# This tests the cons_coord! function directly which should use workspace
118+
c_test = zeros(nlp.meta.ncon)
119+
rows_test = zeros(Int, nlp.meta.nnzj)
120+
cols_test = zeros(Int, nlp.meta.nnzj)
121+
vals_test = zeros(nlp.meta.nnzj)
122+
123+
# First call to cons_coord! to initialize workspace
124+
cons_coord!(nlp, x, c_test, rows_test, cols_test, vals_test)
125+
126+
# Second call should have minimal allocations
127+
workspace_allocations = @allocated begin
128+
cons_coord!(nlp, x, c_test, rows_test, cols_test, vals_test)
129+
end
130+
131+
println("Direct cons_coord! allocations (after warmup): $workspace_allocations bytes")
132+
133+
# Direct workspace usage should have very low allocations
134+
@test workspace_allocations < initial_allocations
135+
136+
else
137+
# If workspace not implemented yet, allocations will be similar
138+
# Test that function still works correctly
139+
@test second_allocations >= 0 # Just ensure it doesn't error
140+
@test initial_allocations > 0 # Ensure some allocation happened
141+
end
142+
143+
finally
144+
finalize(nlp)
145+
end
146+
end
147+
148+
@testset "Workspace Vector Initialization" begin
149+
# Test that the model has the required workspace vectors
150+
nlp = CUTEstModel("HS9")
151+
152+
try
153+
# Print field names for debugging
154+
field_names = fieldnames(typeof(nlp))
155+
println("CUTEstModel fields: ", field_names)
156+
157+
# Check if workspace vectors exist
158+
jac_workspace_fields = [:jac_coord_rows, :jac_coord_cols, :jac_coord_vals, :cons_vals]
159+
hess_workspace_fields = [:hess_coord_vals]
160+
161+
for field in jac_workspace_fields
162+
if field in field_names
163+
println("✓ Found workspace field: $field")
164+
@test isdefined(nlp, field)
165+
if isdefined(nlp, field)
166+
workspace_vec = getfield(nlp, field)
167+
@test isa(workspace_vec, Vector)
168+
println(" - Type: $(typeof(workspace_vec)), Length: $(length(workspace_vec))")
169+
end
170+
else
171+
println("✗ Missing workspace field: $field")
172+
end
173+
end
174+
175+
for field in hess_workspace_fields
176+
if field in field_names
177+
println("✓ Found hessian workspace field: $field")
178+
@test isdefined(nlp, field)
179+
else
180+
println("✗ Missing hessian workspace field: $field")
181+
end
182+
end
183+
184+
finally
185+
finalize(nlp)
186+
end
187+
end

0 commit comments

Comments
 (0)