Weird memory allocation

Every single call of this function results in memory allocation. I have narrowed it down to the call
to Jacobiansurface: if that call is eliminated, there is no memory allocation.

function Jacobianvolume{T<:FESet2Manifold}(self::FEMMBase{T}, J::FFltMat,
            loc::FFltMat, conn::FIntMat, N::FFltMat)::FFlt
  Jac = Jacobiansurface(self, J, loc, conn, N)::FFlt
end

The function that triggers the memory allocation is this one: As you can see, trivial interior and I don’t see why any memory should be allocated since the same arguments are getting passed in.

function Jacobiansurface{T<:FESet2Manifold}(self::FEMMBase{T}, J::FFltMat,
            loc::FFltMat, conn::FIntMat, N::FFltMat)::FFlt
return  1.0
end

I would really appreciate some pointers. I don’t see that deep into how Julia handles these minutiae.
The typealias FFlt can simply be read as Float64.

Thanks very much.

Petr

So Jacobiansurface(self, J, loc, conn, N) is a function that allocates and that’s it?

If Jacobiansurface() does not get called, no allocation; if it does, there is an allocation.

So agrees with what I said?

Sorry, I don’t know if it is the function itself that allocates the memory, or if it is the caller that allocates the memory. But, as I said, if the function doesn’t get called, there is no memory allocated.

I’m not sure what you are asking. If you don’t see allocation if you don’t do something and see allocation if you do something, doesn’t that suggest the thing you do is causing the allocation? What’s wierd about it?

The weird thing is that there IS allocation happening, wouldn’t you agree? Why should a function which simply returns a constant allocate memory? Or, why should the caller allocate memory to call this function? Doesn’t this seem strange to you?

  1. you didn’t describe how you measured this
  2. if you use track-allocation then no it’s not very reliable and can (and in principle is expected to) show allocation from callee.

Measurement: call Jacobianvolume 2 million times, with and without the call to Jacobiansurface. Used the @time macro. With the call there are 2 million more allocations reported than without. Hence, I conclude that calling the Jacobiansurface function causes allocation to occur. I don’t know whether the allocation is in the caller or in the callee.

Please post the exact code you used, rather than describing it in words, since this can affect significantly the result (eg if you are timing in global scope).

@dpsanders:

David,

In principle that is a great suggestion, but that means distilling the code to the absolute minimum. While I’m working on that, do you think anything could be read from the following?

Running

@time for  j = 1:10000000
  Jacobianvolume(femm, J, loc, conn, N) 
end
@code_warntype   Jacobianvolume(femm, J, loc, conn, N) 

produces the timing info
0.297144 seconds (10.00 M allocations: 152.588 MB, 3.84% gc time)

and the listing

Variables:
  #self#::FinEtools.FEMMBaseModule.#Jacobianvolume
  self::FinEtools.FEMMBaseModule.FEMMBase{FinEtools.FESetModule.FESetT3,FinEtools.FEMMBaseModule.#otherdimensionunity}
  J::Array{Float64,2}
  loc::Array{Float64,2}
  conn::Array{Int64,2}
  N::Array{Float64,2}
  Jac::Float64

Body:
  begin 
      SSAValue(0) = FinEtools.FEMMBaseModule.FFlt
      SSAValue(1) = $(Expr(:invoke, LambdaInfo for Jacobiansurface(::FinEtools.FEMMBaseModule.FEMMBase{FinEtools.FESetModule.FESetT3,FinEtools.FEMMBaseModule.#otherdimensionunity}, ::Array{Float64,2}, ::Array{Float64,2}, ::Array{Int64,2}, ::Array{Float64,2}), :(FinEtools.FEMMBaseModule.Jacobiansurface), :(self), :(J), :(loc), :(conn), :(N)))
      Jac::Float64 = SSAValue(1)
      SSAValue(2) = SSAValue(1)
      return SSAValue(2)
  end::Float64

Also,

function Jacobianvolume{T<:FESet2Manifold}(self::FEMMBase{T}, J::FFltMat,
            loc::FFltMat, conn::FIntMat, N::FFltMat)::FFlt
  @code_warntype Jacobiansurface(self, J, loc, conn, N)
  Jac = Jacobiansurface(self, J, loc, conn, N)::FFlt
end

gives

Variables:
  #self#::FinEtools.FEMMBaseModule.#Jacobiansurface
  self::FinEtools.FEMMBaseModule.FEMMBase{FinEtools.FESetModule.FESetT3,FinEtools.FEMMBaseModule.#otherdimensionunity}
  J::Array{Float64,2}
  loc::Array{Float64,2}
  conn::Array{Int64,2}
  N::Array{Float64,2}
  Jac::Float64

Body:
  begin 
      $(Expr(:inbounds, false))
      # meta: location C:\Users\Petr Krysl\Dropbox\Julia\FinEtools\src\FESetModule.jl Jacobian 227
      
SSAValue(0) = FinEtools.FESetModule.FFlt
      $(Expr(:inbounds, true))
      Jac::Float64 = (Base.box)(Base.Float64,(Base.sub_float)((Base.box)(Base.Float64,(Base.mul_float)((Base.arrayref)(J::Array{Float64,2},1,1)::Float64,(Base.arrayref)(J::Array{Float64,2},2,2)::Float64)),(Base.box)(Base.Float64,(Base.mul_float)((Base.arrayref)(J::Array{Float64,2},2,1)::Float64,(Base.arrayref)(J::Array{Float64,2},1,2)::Float64))))
      $(Expr(:inbounds, :pop))
      # meta: pop location
      $(Expr(:inbounds, :pop))
      return Jac::Float64
  end::Float64

I can see the Base.box, and I understand that can mean memory allocation. What I don’t get is why: the expression that gets boxed is @inbounds Jac = (J[1, 1]*J[2, 2] - J[2, 1]*J[1, 2]). Clearly the compiler sees that J is a double array, so why the boxing?

Thanks a lot.

Petr

Yes, you are timing in global scope.
Use the BenchmarkTooks.jl package instead.

No, the timed loop was inside a module, no global variables.

That’s exactly what global scope means

1 Like

Right, sorry. What I meant was the loop is inside a function in a module.

In that case you should still post the actual full code.

1 Like

I tried to create a minimal code to reproduce the conditions, but I failed. The code shown here does not show any allocations:
https://gist.github.com/PetrKryslUCSD/292263d346a1c5653c07cf0dda70b7b2

Nevertheless, there is something interesting here: I get the following for the function called inside the loop from this minimal code (with the line @code_warntype FEMMBaseModule.Jacobianvolume(femm, J, loc, conn, N)):

Variables:
  #self#::FEMMBaseModule.#Jacobianvolume
  self::FEMMBaseModule.FEMMBase{FEMMBaseModule.FESetT3,FEMMBaseModule.#otherdimensionunity}
  J::Array{Float64,2}
  loc::Array{Float64,2}
  conn::Array{Int64,2}
  N::Array{Float64,2}
  Jac::Float64

Body:
  begin 
      SSAValue(0) = FEMMBaseModule.FFlt
      SSAValue(1) = 1.0
      Jac::Float64 = SSAValue(1)
      SSAValue(2) = SSAValue(1)
      return SSAValue(2)
  end::Float64

From the code that reports allocation I get (to my eyes identical):

Variables:
  #self#::FinEtools.FEMMBaseModule.#Jacobianvolume
  self::FinEtools.FEMMBaseModule.FEMMBase{FinEtools.FESetModule.FESetT3,FinEtools.FEMMBaseModule.#otherdimensionunity}
  J::Array{Float64,2}
  loc::Array{Float64,2}
  conn::Array{Int64,2}
  N::Array{Float64,2}
  Jac::Float64

Body:
  begin 
      
SSAValue(0) = FinEtools.FEMMBaseModule.FFlt
      SSAValue(1) = 1.0
      Jac::Float64 = SSAValue(1)
      SSAValue(2) = SSAValue(1)
      return SSAValue(2)
  end::Float64

Yet, in the actual code (not the distilled fragment) each call costs an allocation.
Any ideas please?

Thanks,

Petr

It seems the most likely that you are not measuring what you think you are. If you don’t post the actual code where you see the problem it’ll be impossible to tell what you are getting wrong.

2 Likes

I would post it, but it is several dozen files. I tried to reduce it, but the resulting minimal code does not reproduce the “problem”. But what do you think of the two codes of the called function being “identical”, yet one results in memory allocations, one does not?

@dpsanders, @yuyichao

Here is the code reduced as much as I could. It reproduces the allocation of memory
for each call within the loop.
https://gist.github.com/PetrKryslUCSD/c4c3a348dcef49690ec12bbc5543cc4b

Petr