Very simple code, why would there be performance problems

Hello, everyone
Very simple code, the speed is really slow, I suspect that the method of using Ref in the structure is wrong
Everyone, please take a look, thank you


mutable struct T3
  f1::Int64
  f2::String
  f3::Float64
  EvlP1::Ref{Float64}
  f5::Vector{Int64}
  function T3()
    this = new()
    this.EvlP1 = Ref{Float64}(3.0)
    return this
  end
end

function test() 
  t3=T3()
  #cc=0.1::Float64
  cc=Float64(0.1)
  for i = 1:10^8
    cc = cc +t3.EvlP1[]
  end
  println(cc)  
end

#@time test()
@code_warntype test()

The following is the result of code_warntype, 3 places are red
Variables
#self#::Core.Compiler.Const(test, false)
t3::T3
cc::Any
@_4::Union{Nothing, Tuple{Int64,Int64}}
i::Int64

Body::Nothing
1 ── (t3 = Main.T3())
β”‚β”‚ (cc = Main.Float64(0.1))
β”‚β”‚ %3 = Core.apply_type(Base.Val, 8)::Core.Compiler.Const(Val{8}, false)
β”‚β”‚ %4 = (%3)()::Core.Compiler.Const(Val{8}(), false)
β”‚β”‚ %5 = Base.literal_pow(Main.:^, 10, %4)::Int64
β”‚β”‚ %6 = (1:%5)::Core.Compiler.PartialStruct(UnitRange{Int64}, Any[Core.Compiler.Const(1, false), Int64])
β”‚β”‚ (@_4 = Base.iterate(%6))
β”‚β”‚ %8 = (@_4 === nothing)::Bool
β”‚β”‚ %9 = Base.not_int(%8)::Bool
└└──── goto #4 if not %9
2 β”„β”„ %11 = @_4::Tuple{Int64,Int64}::Tuple{Int64,Int64}
β”‚β”‚ (i = Core.getfield(%11, 1))
β”‚β”‚ %13 = Core.getfield(%11, 2)::Int64
β”‚β”‚ %14 = cc::Any <--------------
β”‚β”‚ %15 = Base.getproperty(t3, :EvlP1)::Ref{Float64} <--------------
β”‚β”‚ %16 = Base.getindex(%15)::Any <--------------
β”‚β”‚ (cc = %14 + %16)
β”‚β”‚ (@_4 = Base.iterate(%6, %13))
β”‚β”‚ %19 = (@_4 === nothing)::Bool
β”‚β”‚ %20 = Base.not_int(%19)::Bool
└└──── goto #4 if not %20
3 ── goto #2
4 β”„β”„ %23 = Main.println(cc)::Core.Compiler.Const(nothing, false)
└└──── return %23

Very slow
3.660509 seconds (200.00 M allocations: 2.980 GiB, 5.72% gc time)

Why are you using a Ref at all here?

5 Likes

Welcome! To echo Oscar’s complaint, you already have a mutable struct, so you don’t need the Ref here.

If you really want the behavior of a C pointer, replace Ref with Base.RefValue{Float64} to avoid using an abstract type.

2 Likes

Aside from what already said, Ref is an abstract type but, for Numbers, it has a constructor and returns value with type Base.RefValue. However, if you use Ref as type then the value boxed and this causes performance degradation and allocations for each access.

4 Likes

Hi @Zq_F,

it would also help to know, what you are up to?

Thank you everyone,
it was resolved after changing to Base.RefValue{Float64}
I am a beginner, ask what method to check if the type is abstract type

There is isconcretetype.

3 Likes

thank you very much

Why not?

mutable struct T3
  f1::Int64
  f2::String
  f3::Float64
  EvlP1::Float64
  f5::Vector{Int64}
  function T3()
    this = new()
    this.EvlP1 = 3.0
    return this
  end
end

function test() 
  t3 = T3()
  cc = 0.1
  for i = 1:10^8
    cc = cc + t3.EvlP1
  end
  println(cc)  
end

@time test()
3.000000001e8
  0.201632 seconds (468.36 k allocations: 21.575 MiB, 1.58% gc time)

You should run it at least twice:

julia> @time test()
3.000000001e8
  0.221929 seconds (466.24 k allocations: 21.460 MiB)

julia> @time test()
3.000000001e8
  0.088707 seconds (9 allocations: 592 bytes)
3 Likes

Julia code gets compiled at runtime. So the first time you call a function you have compilation overhead. However, most of the time you are likely not interested in measuring this overhead. A good way to avoid this issue is to use the BenchmarkTools package to do your benchmarks, which automatically takes care that this does not happen.

Thank you all, the problem has been solved