# Defining types of Structs attributes can sometimes be negatively impacting performance?

``````mutable struct Type4
c
a
b
function Type4(x=1,y=1,z=1)
new(x,y,z)
end
end

mutable struct Type5
a
b
c
function Type5(x::Int64=1, y::Int64=1, z::Int64=1)
new(x,y,z)
end
end

mutable struct Type8
a::Real
b::Real
c::Real
function Type8(x=1,y=1,z=1)
new(x,y,z)
end
end

mutable struct Type9
a::Real
b::Real
c::Real
function Type9(x::Int64=1,y::Int64=1,z::Int64=1)
new(x,y,z)
end
end

using BenchmarkTools
@benchmark Type4()
@benchmark Type5()
@benchmark Type8()
@benchmark Type9()

``````

Not a huge difference but consistent difference.

``````@kwdef mutable struct Type10
a::Real=1
b::Real=1
c::Real=1
end

mutable struct Type11
a
b
c
function Type11(x::Real=1, y::Real=1, z::Real=1)
new(x,y,z)
end
function Type11(x::Number, y::Number,z::Number)
new(x,y,z)
end
end

mutable struct Type12
a::Number
b::Number
c::Number
function Type12(x::Real=1, y::Real=1, z::Real=1)
new(x,y,z)
end
function Type12(x::Number, y::Number,z::Number)
new(x,y,z)
end
end

@benchmark Type10()
@benchmark Type11()
@benchmark Type12()
``````

To me, it seemed like not defining attribute types while defining the struct attributes, but later in the constructor is the best approach.

(Forgive me for my lingoâ€¦ I am not well acquainted with the actual terminology we use and am still a beginner.)

I donÂ´t think those differences in performance are meaningful. But, for performance, what you need is to make the fields of the structs concrete. `Real`, for example, is an abstract type (it can be an integer, a float, etc).

You need either something like:

``````struct A
x::Int
y::Float64
end
``````

where all the fields are concretely typed to specific types, or

``````struct A{T1<:Real,T2<:Real}
x::T1
y::T2
end
``````

where an instance of the struct will have concrete types, but those can be of different types and different subypes of real.

Or some variation of the above constructs, always taking care that the fields are all concretely typed.

3 Likes

waitâ€¦ so using `<:Real` is better than just `Real`?

So whether you define the types in constructors or the actual definition is meaningless eh? (As long as you ARE defining some type to help Julia figure out what functions to dispatch)

Both are important. The declared type in the struct definition is more important, though.

The difference is having that directly on the structure field, as you did, or as a type parameter, as I did. If you set the field to be Real, you are saying that the field can be changed from one type to the other in the same instance of the struct:

``````julia> mutable struct A x::Real end

julia> a = A(1)
A(1)

julia> a.x = 1.3
1.3
``````

When you use a parametric type, you can initialize the structure with the subtypes, but then the structure has a concrete type associate with it:

``````julia> mutable struct B{T<:Real} x::T end

julia> b = B(1)
B{Int64}(1)

julia> b.x = 1.3
ERROR: InexactError: Int64(1.3)
``````
4 Likes

Of note is that the declared field type has no bearing on the dispatch behavior of a value retrieved from that field. For example:

``````julia> struct Foo
a::Real
end

julia> bar(::Real) = "Real fallback"
bar (generic function with 1 method)

julia> bar(::Int) = "Int specialization"
bar (generic function with 2 methods)

julia> bar(Foo(1).a)
"Int specialization"

julia> bar(Foo(1.0).a)
"Real fallback"
``````

When you say a struct field has type `Real`, all youâ€™re saying is â€śthis field can hold objects of any subtype of `Real`â€ť. Type inference will use that information to narrow down the possibilities for dispatching, but it wonâ€™t otherwise influence dispatch behavior.

One consequence of declaring a field with an abstract type is that the object actually stored there is no longer stored in-line, because the original type needs to be preserved for dynamic dispatch to work. Itâ€™s this indirection & dynamic type checks that cause a slowdown in hot loops.

2 Likes

Ahhhhhh I see!

You are only timing the constructor. It could be there are some differences there, but they donâ€™t seem to be significant.

The big performance difference is when you are using the struct. And the difference is whether the types of the fields are concrete or not.

``````struct MyStruct1
x::Real
end

struct MyStruct2{T <: Real}
x::T
end
``````

Now, consider these calls:

``````f(arg) = 2arg.x

x1 = MyStruct1(2)
x2 = MyStruct2(2)

f(x1)
f(x2)
``````

When `f(x1)` is about to be run, the compiler looks at the `f`, and at `x1`. Ok, the `x1` is of type `MyStruct1`, it has a field of type `Real`. Iâ€™ll compile a version of `f` for `MyStruct1`. It does, and figures out that `arg.x` should be multiplied by 2. But it doesnâ€™t know the type of `arg.x`, only that itâ€™s a `Real`. So instead of simple `mul` instruction it creates code for finding a multiply function for whatever type `arg.x` happens to be. Itâ€™ll take some hundreds of clock ticks.

On the other hand, the `f(x2)` is easier for the compiler. It sees that `x2` is of type `MyStruct2{Int}`. It compiles an instance of `f` for the type `MyStruct2{Int}`. It finds that the `2arg.x` is multiplying the `Int` `arg.x` by the `Int` 2. So the compiler emits a single `mul` instruction (or even just a left shift). The function takes at most a clock tick.

That makes it even more clear. Thank you!