Improving performance due to setting types of the fields of a struct

Hi,

I have a question about improving the performance of Julia. In the Julia documentation is something explained about setting types of a structs.

"
You can do better by declaring the type of a . Here, we are focused on the case where a might be any one of several types, in which case the natural solution is to use parameters. For example:

julia> mutable struct MyType{T<:AbstractFloat}
           a::T
       end

This is a better choice than

julia> mutable struct MyStillAmbiguousType
           a::AbstractFloat
       end

because the first version specifies the type of a from the type of the wrapper object.
"

My question is now: Is is also better, if you have multiple fields of the struct
In example:

julia> mutable struct MyType{T<:Float64, I<:Int64}
           a::T 
           b::T
           c::I
           d::I
       end
julia> mutable struct MyStillAmbiguousType
           a::Float64
           b::Float64
           c::Int64
           d::Int64
       end

yes,no,maybe. You are doing the same thing two different ways.

[mutable] struct Test{F<:AbstractFloat, I<:Integer}
    afloat::F
    anint::I
    anotherint::I 
end

does more for you.

The next thing to consider is “does this struct need to be mutable?”
If you usually keep the initial values, your performance will likely increase by using struct rather than mutable struct. On the occasions where you change a value, just create a new struct – if that is appropriate to your data flow.

From your question, I worry you’re missing the point of that section: the problem is with the AbstractFloat, which as an abstract type has a potentially-limitless set of subtypes. For type declarations of the form MyStillAmbiguousType (the version in the docs, not the one you asked about) there is no way to know the type of obj.a given that you have an object obj of type MyStillAmbiguousType. Conversely, MyType{Float32} makes it plain that obj.a will be a Float32. Finally, MyType{AbstractFloat} will behave identically to MyStillAmbiguousType; the difference is the possibility of using a concrete type as the type parameter.

The two types you asked about are identical: the only subtype of Float64 is Float64, and the only subtype of Int64 is Int64, so there’s really nothing parametric about the first one and nothing ambiguous about the second.

7 Likes

The difference between these two types is a bit subtle:

mutable struct Param{T<:Integer}
    field::T
end

mutable struct NoParam
    field::Integer
end

I fear that it may have been lost along the way in edits to the manual over time. To clarify:

  • the value of .field of instances of both Param and NoParam can be changed, but
  • the type of .field of an instance of Param cannot be changed, whereas
  • the type of .field of an instance of NoParam can be changed.

In other words:

julia> p = Param(123)
Param{Int64}(123)

julia> n = NoParam(123)
NoParam(123)

julia> p.field = big(2)^1000
ERROR: InexactError: Int64(10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376)
Stacktrace:
 [1] Type at ./gmp.jl:354 [inlined]
 [2] convert at ./number.jl:7 [inlined]
 [3] setproperty!(::Param{Int64}, ::Symbol, ::BigInt) at ./Base.jl:21
 [4] top-level scope at REPL[5]:1

julia> n.field = big(2)^1000
10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376

Moreover, the type of .field for a Param object is reflected in the type of the Param object, so when the compiler generates code for a specific kind of Param object, it can specialize on the type of .field as well. On the other hand, the type of .field of NoParam is not captured by the type of the NoParam instance and so the generated code is not specialized and has to be general enough to handle any kind of Integer that it might encounter there.

16 Likes

Perhaps we could add this example to the docs?

I think I did once. Feel free to copy it back in :slight_smile:

2 Likes