-
-
Couldn't load subscription status.
- Fork 410
Create containers with map instead of for loops #2070
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
ecb7915 to
2d24a94
Compare
2d24a94 to
05f4242
Compare
Codecov Report
@@ Coverage Diff @@
## master #2070 +/- ##
========================================
- Coverage 91.4% 91.1% -0.3%
========================================
Files 33 38 +5
Lines 4246 4318 +72
========================================
+ Hits 3881 3934 +53
- Misses 365 384 +19
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks promising! I expect to be able to do a real review over the weekend.
| ```jldoctest constraint_arrays; setup=:(model=Model(); @variable(model, x)) | ||
| julia> @constraint(model, con[i = 1:3], i * x <= i + 1) | ||
| 3-element Array{ConstraintRef{Model,C,Shape} where Shape<:AbstractShape where C,1}: | ||
| 3-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},1}: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a pretty bad printing experience.
b6b9fa3 to
3a7fa95
Compare
3a7fa95 to
63ff9cc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great, it addresses a design issue that goes back quite a long time. Could you run benchmarks to make sure there's no significant regression?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, I still recommend some benchmarks.
| on the `IteratorSize` of the elements of `prod.iterators`. | ||
| Cartesian product of the iterators `prod.iterators`. It is the same iterator as | ||
| `Base.Iterators.ProductIterator` except that it is independent of the | ||
| `IteratorSize` of the elements of `prod.iterators`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this could benefit from a longer comment about why the behavior of Base.Iterators.ProductIterator wasn't sufficient and required a wrapper. (Maybe outside the docstring.) What breaks if we use Base.Iterators.ProductIterator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact, every test passed with Iterators.ProductIterator but
https://github.com/JuliaOpt/JuMP.jl/blob/1a4f663504606464292caf2db2d60acba6991af3/examples/cutting_stock_column_generation.jl#L149
fails.
Now I have added tests in test/Containers/ covering this feature.
|
I added benchmarks in the initial PR comment. |
|
The numbers from |
|
There is a small performance hit for creating function container(f::Function, indices,
::Type{SparseAxisArray})
mappings = map(I -> I => f(I...), indices)
data = Dict(mappings)
if length(mappings) != length(data)
error("Repeated index ...")
end
return SparseAxisArray(Dict(data))
endbecause we allocate function container(f::Function, indices,
::Type{SparseAxisArray})
mappings = Base.Generator(I -> I => f(I...), indices)
data = Dict(mappings)
if length(mappings) != length(data)
error("Repeated index ...")
end
return SparseAxisArray(Dict(data))
endbut then |
| Same as `Dict{K, V}` but errors if constructed from an iterator with duplicate | ||
| keys. | ||
| """ | ||
| struct NoDuplicateDict{K, V} <: AbstractDict{K, V} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to define a new type that's only used in one function? What about a function that takes a generator, constructs a Dict with errors on duplicates, and returns Dict?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
constructs a
Dictwith errors on duplicates
How do you do that ? That's exactly NoDuplicateDict is trying to do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't dig too deeply, but my question was why you can't define a function like:
function dict_no_duplicates{K, V}(it) where {K, V}
dict = Dict{K, V}()
for (k, v) in it
if haskey(dict, k)
error(...)
else
dict[k] = v
end
end
return dict
endThis doesn't require defining a new type. Maybe I'm missing something related to dict_with_eltype.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is that we don't know the type of the keys and values. The challenge is that Julia wants to infer it in a way that's friendly with Julia compiler and inference system.
So it starts iterating, assumes the type is always the same so it build the container with the type of the first element and then if a new element does not fit, it creates a new container with a wider type, copy and continue.
This is what is done in map (that DenseAxisArray rely on) and dict_with_eltype (that SparseAxisArray rely on).
|
There seems to be something fishy with julia> f() = (it = Containers.nested(() -> 1:1000, condition = iseven); @time map(i -> 1, it); return)
f (generic function with 2 methods)
julia> f()
0.020841 seconds (79.05 k allocations: 3.991 MiB)
julia> f()
0.000392 seconds (3.50 k allocations: 125.422 KiB)
julia> f() = (it = Iterators.filter(iseven, 1:1000); @time map(i -> 1, it); return)
f (generic function with 2 methods)
julia> f()
0.000013 seconds (10 allocations: 8.406 KiB)
julia> f()
0.000023 seconds (10 allocations: 8.406 KiB)The fact that EDIT should be improved by 1192fd8 |
|
The performance issue with |
| Same as `Dict{K, V}` but errors if constructed from an iterator with duplicate | ||
| keys. | ||
| """ | ||
| struct NoDuplicateDict{K, V} <: AbstractDict{K, V} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't dig too deeply, but my question was why you can't define a function like:
function dict_no_duplicates{K, V}(it) where {K, V}
dict = Dict{K, V}()
for (k, v) in it
if haskey(dict, k)
error(...)
else
dict[k] = v
end
end
return dict
endThis doesn't require defining a new type. Maybe I'm missing something related to dict_with_eltype.
Currently, when creating a container, JuMP macros first allocate the container and then fill it with for loops.
The disadvantage of this approach is that we need to determine the type beforehand so the
eltypeis for instance not concrete for@constraintand@expressiononly works forAffExpr(see #525).With this PR, we rely on
mapto create the array holding the values of the container hence we don't need to determine theeltypebeforehand hence also removing the need forvariable_typeandconstraint_type(but we should keep them to avoid making a breaking change).The PR also lays out foundations for creating constrained variables as it was needed to update the creation of SDP and symmetric matrix of variables with the new design.
As a proof of concept that the container logic is now completely decoupled from JuMP, the
Containerssubmodule has now a@container.Closes #525
Benchmarks
For the
test/perf/speed.jlbenchmark, before the PR:After the PR:
The speedup is between 25% and 50% (i.e. 2x) which is nice to see!
I suspect the speedup to come from the fact that the containers of constraint now have concrete element type. This was the case in JuMP v0.18 IIRC but not anymore for JuMP v0.19 so this PR may help with #1403
For the
test/perf/axis_constraints.jlbenchmark, before this PR:After this PR:
We see a speedup on computing the sum of the duals and less allocation because the
eltypeof the containers are concrete.