Performance speed up when wrapping script with Juno.@profiler

Hello, I’m trying to simulate the Belousov-Zhabotinsky (BZ) reaction as implemented here.

I have written the following implementation for that purpose:

using Images, ImageView, ProgressMeter

function react(petridish::Matrix{<:RGB}, α, β, γ, kernel)
  # Count the average amount of each species in the 8 cells around each cell
  # through a convolution with a 3x3 mask
  s = imfilter(petridish, kernel)
  # Apply the reaction equations
  react!(s, α, β, γ)
  return s
end

function react!(
    s,
    α::Number,
    β::Number,
    γ::Number
  )
  @inbounds for ii in eachindex(s)
    # Get components
    ss = s[ii]
    r = red(ss)
    g = green(ss)
    b = blue(ss)
    # Compute new components
    new_r = clamp(r * (1 + α*g - γ*b), 0, 1)
    new_g = clamp(g * (1 + β*b - α*r), 0, 1)
    new_b = clamp(b * (1 + γ*r - β*g), 0, 1)
    # Update s
    s[ii] = RGB(new_r, new_g, new_b)
  end
end

function simulate(petridish, nx, ny, max_iter, α, β, γ, kernel)
  animation = zeros(RGB{N0f8}, (nx, ny, max_iter));
  @showprogress for ii in 1:max_iter
    petridish = react(petridish, α, β, γ, kernel)
    animation[:, :, ii] = petridish
  end
  return animation
end

nx, ny = 1000, 1000
α, β, γ = 1, 1, 1.2
max_iter = 500
kernel = centered(ones(3, 3) / 9)
init_petridish = rand(RGB{N0f8}, (nx, ny));
petridish = simulate(init_petridish, nx, ny, max_iter, α, β, γ, kernel)
imshow(petridish)

My problem is that when trying to run this code by either running an include or directly pasting into a REPL, the code is very slow, in the hours. So I decided to figure out if I could find the bottleneck with Juno’s @profiler function but it turns out this script runs a lot faster (~seconds) just by doing this.

Is the problem with my code or with some package I’m using?

TLDR: The speedup occurs when running Juno.@profiler include("bzreaction.jl") in the Atom IDE with Juno installed

When setting up a test like this

function test()
  nx, ny = 1000, 1000
  α, β, γ = 1, 1, 1.2
  max_iter = 500
  kernel = centered(ones(3, 3) / 9)
  init_petridish = rand(RGB{N0f8}, (nx, ny));
  petridish = simulate(init_petridish, nx, ny, max_iter, α, β, γ, kernel)
end

imshow(test())

your program is running in half a minute on my system. To profile I’d use something like

function test()
  nx, ny = 1000, 1000
  α, β, γ = 1, 1, 1.2
  max_iter = 500
  kernel = centered(ones(3, 3) / 9)
  init_petridish = rand(RGB{N0f8}, (nx, ny));
  petridish = simulate(init_petridish, nx, ny, max_iter, α, β, γ, kernel)
end

# imshow(test())

using Profile
Profile.clear()
test()
@profile test()
# Profile.print()
Juno.profiler()

I can’t reproduce this behaviour. My recommendation would be to isolate the simulation. Maybe you have some issue with the used progress meter or the image viewer.

1 Like

It could be a multithreading issue while loading ImageView.jl which ultimately is caused by some GTK.jl interaction (also here).

Maybe the Juno profiler runs the code with a single thread and doesn’t trigger this issue?

Did you try to start Julia with only one thread?

2 Likes

Indeed, this is the problem.

Running it with 1 thread (julia -t 1) takes me back to a ~10 second run time.

Thank you @roflmaostc !

1 Like

BTW, which part is multi-threaded in your code?

I guess it’s only imfilter since the other code looks single threaded.