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.

17 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

Hi,

can somebody explain me what I did wrong in this code. It seems that I didn´t know the right syntax for multiple variables

struct AlgorithmOpt{F<:Float64, S<:String, I<:Int64, B<:Bool}
    sorting::S
    selection::S
    replacement::S
    crossover::S
    mutation::S
    num_size::I
    max_generations::I
    verbose::B
end

Because I get the following MethodError

ERROR: LoadError: MethodError: no method matching EaOpt.AlgorithmOpt(::String, ::String, ::String, ::String, ::String, ::Int64, ::Int64, ::Bool)

You have the F there which cannot be determined based on the arguments.

However, I would really advice not to include parameters when they can only take a concrete type. Just put the type directly after the field instead.

2 Likes

Ok thanks. Then I misunderstood the statemant of StefanKarpinski that it would help the compiler and lead to faster code.

The distinction here is whether you have a concrete type (such as Int64) or a more general type (such as Integer which could be a Int64 or Int16 or …).

@StefanKarpinski said above, if you have an Integer the compiler can generate faster code if you use parameters, because the field of a Param{Int64} can only ever be an Int64. The field of a NoParam can change between the different concrete types of Integer, so it’s harder to generate specialized code because the compiler can’t know what sort of Integer to expect.

However, if you only ever want to use one type of Integer (e.g. Int64, as in your code) @kristoffer.carlsson advice applies: use

struct A
    field::Int64
end

instead of

struct A{I <: Int64}
   field::I
end

In this case it makes no difference for the compiler because in both cases field can only ever be Int64.

That said, in many cases you don’t really care what type of Integer is used and in that cases it would be beneficial to use parameters and a more liberal type to give the users the choice what kind of Integers they would like to use.

1 Like

Thanks for the explanation. Now I got it