[SOLVED] 37x performance hit when wrapping Refs. Any solution?

Problem

I have found that wrapping Ref instances in a struct leads to a sizable performance hit. Here a MWE:

using BenchmarkTools

struct Foo
  ref::Ref{Int}
  Foo(a::Int) = new(Ref(a))
end

work(i,ref::Ref) = sqrt(i*ref[])
@noinline work(i,foo::Foo) = work(i,foo.ref)

function loop(n,x)
  q = 0.0
  for i in 1:n
    for j in 1:10
      q += work(i,x)
    end
  end
  q
end

function main(n)

  foo= Foo(2)
  println("Benchmark for Foo:")
  @btime loop($n,$foo)

  ref = Ref(2)
  println("Benchmark for Ref:")
  @btime loop($n,$ref)

end

 main(1000000)

which gives this output:

Benchmark for Foo:
  832.085 ms (29994890 allocations: 457.69 MiB)
Benchmark for Ref:
  22.336 ms (0 allocations: 0 bytes)

Note that using the simple wrapper Foo leads to a 37x time increase wrt using the plain Ref.

Question

Is anybody aware of this? Is there any solution/workaround to this problem?

Thanks in advance for helping!

Ref is an abstract type so this is expected. The concrete type is Base.RefValue{Int} for Ref{Int}(.). If you want to use ref value in a struct you can make it parametric to the ref value type for better alternative.

struct Foo{R}
  ref::R
  Foo(a::Int) = new(Ref(a))
end
6 Likes

@tomaklutfu Thanks for the (very useful) answer! I was completely unaware that Ref was an abstract type!