3

I am trying to create an animation with three subplots (one surface, two heatmaps) in Julia using Plots.jl with the GR backend. By far the slowest part of my code is the generation of these plots, so I am trying to find the most efficient way to do it.

I've tried re-calling the plotting inside the animate loop, but that was significantly slower than modifying in place as so:

using Plots,Profile

function mcve(n)
    A = rand(n,100,100)
    B = rand(n,100,100)

    l = @layout [ a b ; c]
    p1 = surface(1:100,1:100,A[1,:,:],clims=(0,1),legend=false)
    p2 = heatmap(A[1,:,:],clims=(0,1),aspect_ratio=1,legend=false)
    p3 = heatmap(B[1,:,:],aspect_ratio=1)
    p = plot(p1,p2,p3,layout = l)

    anim = @animate for i=1:n
        surface!(p[1],1:100,1:100,A[i,:,:])
        heatmap!(p[2],A[i,:,:])
        heatmap!(p[3],B[i,:,:])
    end
    gif(anim,"example.gif")
end

mcve(1)
@profile mcve(10)
Profile.print()

Results in the trace https://pastebin.com/Lv9uCLE5

According to the profiler, nearly half the runtime is spent in a function "setcharheight", which is calling a c library. Is there a way to reduce the number of calls to it I need to make?

James C
  • 143
  • 7
  • what [backend](http://docs.juliaplots.org/latest/backends/) are you using? posting the profiler report would help. a [minimum, complete and verifiable example](https://stackoverflow.com/help/mcve) would help even better. – Felipe Lema Apr 23 '19 at 13:36
  • Thanks for the feedback. I made a better example, but the profiler report is well over the 30,000 character limit, even for my new stripped down example. Is a link to pastebin appropriate? – James C Apr 24 '19 at 01:27
  • I couldn't reproduce using latest Plots module. `backend_name()` returns `:gr`. Are you using the Plotly backend? – Felipe Lema Apr 24 '19 at 20:20
  • No, I'm using the default, which is GR. Though I discovered that I could avoid the problem by setting xticks and yticks to false, resulting in a speedup of approximately 6 times. – James C Apr 25 '19 at 02:26

1 Answers1

5

I did some experiments, and I found two things that could dramatically speed up the plotting process.

First, rather than redrawing the plots using surface!() and heatmap!(), I simply replaced their :z series. This is shown by comparing the first to the third and second to the fourth functions in the code example.

Second, GR.jl; setcharheight is extremely slow. This is likely due to the ccall(), which means it may be OS dependent. By setting xticks and yticks to false, significant speedups were achieved. This is shown by comparing the first to second and third to fourth functions in the example.

using Plots

function mcve(n,A,B)

    l = @layout [ a b ; c]
    p1 = surface(1:100,1:100,A[1,:,:],clims=(0,1),legend=false)
    p2 = heatmap(A[1,:,:],clims=(0,1),aspect_ratio=1,legend=false)
    p3 = heatmap(B[1,:,:],aspect_ratio=1)
    p = plot(p1,p2,p3,layout = l)

    anim = @animate for i=1:n
        surface!(p[1],1:100,1:100,A[i,:,:])
        heatmap!(p[2],A[i,:,:])
        heatmap!(p[3],B[i,:,:])
    end
    gif(anim,"example1.gif")
end

function mcve4(n,A,B)

    l = @layout [ a b ; c]
    p1 = surface(1:100,1:100,A[1,:,:],clims=(0,1),legend=false,xticks=false,yticks=false)
    p2 = heatmap(A[1,:,:],clims=(0,1),aspect_ratio=1,legend=false,xticks=false,yticks=false)
    p3 = heatmap(B[1,:,:],aspect_ratio=1,xticks=false,yticks=false)
    p = plot(p1,p2,p3,layout = l)

    anim = @animate for i=1:n
        surface!(p[1],1:100,1:100,A[i,:,:],xticks=false,yticks=false)
        heatmap!(p[2],A[i,:,:],xticks=false,yticks=false)
        heatmap!(p[3],B[i,:,:],xticks=false,yticks=false)
    end
    gif(anim,"example4.gif")
end


function mcve2(n,A,B)

    l = @layout [ a b ; c]
    p1 = surface(1:100,1:100,A[1,:,:],clims=(0,1),legend=false,xticks =false,yticks= false)
    p2 = heatmap(A[1,:,:],clims=(0,1),aspect_ratio=1,legend=false,xticks = false,yticks= false)
    p3 = heatmap(B[1,:,:],aspect_ratio=1,xticks = false,yticks= false)
    p = plot(p1,p2,p3,layout = l)

    anim = @animate for i=1:n
        p[1][1][:z] = A[i,:,:]
        p[2][1][:z] = A[i,:,:]
        p[3][1][:z] = B[i,:,:]
    end
    gif(anim,"example2.gif")
end


function mcve3(n,A,B)

    l = @layout [ a b ; c]
    p1 = surface(1:100,1:100,A[1,:,:],clims=(0,1),legend=false)
    p2 = heatmap(A[1,:,:],clims=(0,1),aspect_ratio=1,legend=false)
    p3 = heatmap(B[1,:,:],aspect_ratio=1)
    p = plot(p1,p2,p3,layout = l)

    anim = @animate for i=1:n
        p[1][1][:z] = A[i,:,:]
        p[2][1][:z] = A[i,:,:]
        p[3][1][:z] = B[i,:,:]
    end
    gif(anim,"example3.gif")
end

A = rand(1,100,100)
B = rand(1,100,100)

mcve(1,A,B)
mcve2(1,A,B)
mcve3(1,A,B)
mcve4(1,A,B)

A = rand(10,100,100)
B = rand(10,100,100)

println("Replot,ticks on")
@time mcve(10,A,B)
println("Replot,ticks off")
@time mcve4(10,A,B)
println(":z replace, ticks on")
@time mcve3(10,A,B)
println(":z replace, ticks off")
@time mcve2(10,A,B)

which results in

Replot,ticks on
 19.347849 seconds (12.78 M allocations: 399.848 MiB, 0.30% gc time)
Replot,ticks off
  6.227432 seconds (8.71 M allocations: 298.890 MiB, 0.88% gc time)
:z replace, ticks on
  8.572728 seconds (5.43 M allocations: 149.359 MiB, 0.24% gc time)
:z replace, ticks off
  1.805316 seconds (1.36 M allocations: 48.450 MiB, 0.40% gc time)
James C
  • 143
  • 7