Slow makie animations?

Hi,
I’m trying to animate some makie plots and performance seems to be a bit lacking.
Animating this plot takes about 47 seconds on my machine
47.157643 seconds (30.39 M allocations: 3.480 GiB, 1.38% gc time, 3.92% compilation time)

This is the animation script.

using CairoMakie

function draw_me(N, Z, n_frames)
    tb = 1:N

    tr_1 = randn(N)
    tr_2 = randn(N, Z, n_frames)

    # Observables give the x-coordinate of the vertical time bar
    ix_t = Observable(1)

    timebar_x = @lift [tb[$ix_t], tb[$ix_t]]
    current_frame = @lift tr_2[:, :, $ix_t]

    fig = Figure()
    ax_l = Axis(fig[1, 1])
    ax_c = Axis(fig[1, 2])

    lines!(ax_l, tb, tr_1)
    lines!(ax_l, timebar_x, [-2, 2], color=:red, linewidth=3)
    contourf!(ax_c, tb, 1:Z, current_frame)

    @time record(fig, "test.mp4", 1:n_frames,
        framerate=5) do t
        ix_t[] = t
    end

end

draw_me(100, 30, 30)

I’m also seeing only 100% CPU utilization, is there maybe a way to parallelize this?

Contourf is pretty slow… How much faster is it if you do the animation without it?

Without the contour plot:

 1.677212 seconds (1.41 M allocations: 96.226 MiB, 4.43% gc time, 75.97% compilation time)

Maybe try using the lower-level recordframe! interface?

Random matrices are very taxing with contourf because they lead to a lot of tiny polygons. If you use more structured data, is it faster?

Saving the files instead of animating is about 50% faster.

function draw_me_save(N, Z, n_frames)
    tb = 1:N

    tr_1 = randn(N)
    tr_2 = randn(N, Z, n_frames)

    for ix_f ∈ 1:n_frames
        timebar_x = [tb[ix_f], tb[ix_f]]
        current_frame = tr_2[:, :, ix_f]

        fig = Figure()
        ax_l = Axis(fig[1, 1])
        ax_c = Axis(fig[1, 2])

        lines!(ax_l, tb, tr_1)
        lines!(ax_l, timebar_x, [-2, 2], color=:red, linewidth=3)
        contourf!(ax_c, tb, 1:Z, current_frame)

        save("test_frame_" * lpad(ix_f, 2, "0") * ".png", fig)
    end
end

@time draw_me_save(100, 30, 30)

22.918960 seconds (47.75 M allocations: 2.560 GiB, 3.02% gc time, 0.05% compilation time)

Followed by ~2s for ffmpeg:

time ffmpeg -framerate 5 -pattern_type glob -i 'test_frame_*.png' -c:v libx264 -pix_fmt yuv420p out.mp4

This is code is an MWE for my data that is more regular. Performance is bad there too.

Using recordframe! gives about the same performance as plotting each frame individually, saving to png, and ffmpeg-ing it:

using CairoMakie

function draw_me(N, Z, n_frames)
    tb = 1:N

    tr_1 = randn(N)
    tr_2 = randn(N, Z, n_frames)

    # Observables give the x-coordinate of the vertical time bar
    ix_t = Observable(1)

    timebar_x = @lift [tb[$ix_t], tb[$ix_t]]
    current_frame = @lift tr_2[:, :, $ix_t]

    fig = Figure()
    ax_l = Axis(fig[1, 1])
    ax_c = Axis(fig[1, 2])

    lines!(ax_l, tb, tr_1)
    lines!(ax_l, timebar_x, [-2, 2], color=:red, linewidth=3)
    contourf!(ax_c, tb, 1:Z, current_frame)

    record(fig, "test.mp4", 1:n_frames,
        framerate=5) do t
        ix_t[] = t
    end
end


function draw_me_save(N, Z, n_frames)
    tb = 1:N

    tr_1 = randn(N)
    tr_2 = randn(N, Z, n_frames)

    for ix_f ∈ 1:n_frames
        timebar_x = [tb[ix_f], tb[ix_f]]
        current_frame = tr_2[:, :, ix_f]

        fig = Figure()
        ax_l = Axis(fig[1, 1])
        ax_c = Axis(fig[1, 2])

        lines!(ax_l, tb, tr_1)
        lines!(ax_l, timebar_x, [-2, 2], color=:red, linewidth=3)
        contourf!(ax_c, tb, 1:Z, current_frame)

        save("test_frame_" * lpad(ix_f, 2, "0") * ".png", fig)
    end
    println("To make movie execute:")
    println("ffmpeg -framerate 5 -pattern_type glob -i 'test_frame_*.png' -c:v libx264 -pix_fmt yuv420p out.mp4")
end


function draw_me_lowlevel(N, Z, n_frames)
    tb = 1:N

    tr_1 = randn(N)
    tr_2 = randn(N, Z, n_frames)

    fig = Figure()
    ax_l = Axis(fig[1, 1])
    ax_c = Axis(fig[1, 2])

    l1 = lines!(ax_l, tb, tr_1)   

    record(fig, "test_low.mp4") do io
        for ix_t ∈ 1:n_frames
            timebar_x = [tb[ix_t], tb[ix_t]]
            current_frame = tr_2[:, :, ix_t]

            l2 = lines!(ax_l, timebar_x, [-2, 2], color=:red, linewidth=3)
            c = contourf!(ax_c, tb, 1:Z, current_frame)

            recordframe!(io)

            delete!(ax_l, l2)
            delete!(ax_c, c)
        end
    end
end

println("Low-level interface")
@time draw_me_lowlevel(100, 30, 30)
println("Writing frames as png")
@time draw_me_save(100, 30, 30)
println("Observables and @lift macros")
@time draw_me(100, 30, 30)

gives me

Testing low-level
 26.291187 seconds (36.92 M allocations: 2.240 GiB, 2.55% gc time, 23.76% compilation time)
Writing frames as png
To make movie execute:
ffmpeg -framerate 5 -pattern_type glob -i 'test_frame_*.png' -c:v libx264 -pix_fmt yuv420p out.mp4
 23.013333 seconds (48.09 M allocations: 2.580 GiB, 2.96% gc time, 1.27% compilation time)
Observables and @lift macros
 46.522218 seconds (29.64 M allocations: 3.416 GiB, 1.16% gc time, 1.94% compilation time)

I’m glad that this worked out for you. However, note that you can still use Observables with recordframe!.

Thanks. I couldn’t find this in the documentation: API · Makie
Do you maybe have an example?