# Julia sets in Julia, using Interact.jl - I'd like to get it faster

**URL:** https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533
**Category:** Performance
**Created:** [March 4, 2020, 7:48pm UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533 "2020-03-04T19:48:27Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![zenon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zenon/32/6508_2.png) [@zenon](https://discourse.julialang.org/u/zenon)
#### Post date: [March 4, 2020, 7:48pm UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/1 "2020-03-04T19:48:27Z")

</div>

Hi, after I talked about Julia to collegues some weeks ago, I somehow wanted to plot Julia sets ([Wikipedia](https://en.wikipedia.org/wiki/Julia_set)), simply because I thougt it to be a funny example.

To play with it I used Interact.jl.  
The current result is this 82 liner: [Github gist](https://gist.github.com/zenon/88e30f7894bf38fcc161699a1760dc22)

I tried to get rid of slow things I knew about:

- use existing arrays instead of creating new ones
- tried to avoid type changes

Then I attempted to utilize my 8 core Ryzen with 15 threads, by partitioning the image (its hight) into batches, and use @threads

This is faster, but much less than I hoped for:

With threads:

julia\> @time juliaSet(-0.62+0.42im)  
0.442297 seconds (19.41 M allocations: 321.882 MiB, 22.05% gc time)

Without:  
julia\> @time juliaSet(-0.62+0.42im)  
1.208751 seconds (19.12 M allocations: 317.792 MiB, 4.61% gc time)

In addition the plot takes time of course.

Can I do better?

(This is rather a fun example, so I mainly do it for the fun of it, and to learn.)

Kind greetings, z.

 ![juliaset](https://global.discourse-cdn.com/julialang/original/3X/5/7/57000df8853e49139ad8cf0fc589ed0eb0a0485c.png)

---

<div class="post-metadata">

### Author: ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)
#### Post date: [March 4, 2020, 7:58pm UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/2 "2020-03-04T19:58:19Z")

</div>

Check out the [performance tips](https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-global-variables-1), particularly the very first one, which is “Avoid global variables”. Your code is entirely based on non-constant global variables, which have a _huge_ negative performance impact. If you can rewrite it to avoid global variables entirely you should see a significant improvement.

---

<div class="post-metadata">

### Author: ![zenon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zenon/32/6508_2.png) [@zenon](https://discourse.julialang.org/u/zenon)
#### Post date: [March 4, 2020, 8:05pm UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/3 "2020-03-04T20:05:52Z")

</div>

Ah. Great resource, thank you!

Updated to local variables now. The line

@time juliaSet(batches, valueGrid, dataGrid, c)

is now called inside a function.

julia\> j()  
0.351686 seconds (19.11 M allocations: 317.267 MiB)

Slightly faster.

I should add:

julia\> versioninfo()  
Julia Version 1.3.0  
Commit 46ce4d7933 (2019-11-26 06:09 UTC)  
Platform Info:  
OS: Windows (x86\_64-w64-mingw32)  
CPU: AMD Ryzen 7 3700X 8-Core Processor  
WORD\_SIZE: 64  
LIBM: libopenlibm  
LLVM: libLLVM-6.0.1 (ORCJIT, znver1)  
Environment:  
JULIA\_NUM\_THREADS = 16

I will try on tomorrow …

---

<div class="post-metadata">

### Author: ![BeastyBlacksmith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/beastyblacksmith/32/4741_2.png) [@BeastyBlacksmith](https://discourse.julialang.org/u/BeastyBlacksmith)
#### Post date: [March 4, 2020, 9:14pm UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/4 "2020-03-04T21:14:15Z")

</div>

Checkout the julia fractal code from @sdanisch [here](https://nextjournal.com/sdanisch/julia-gpu-programming), but keep in mind that this was pre 1.0 and I don’t know the current state of GPUArrays, but same code should also work with CUArrays.

---

<div class="post-metadata">

### Author: ![zenon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zenon/32/6508_2.png) [@zenon](https://discourse.julialang.org/u/zenon)
#### Post date: [March 4, 2020, 9:19pm UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/5 "2020-03-04T21:19:49Z")

</div>

Interesting. I got a AMD card, so I’ll see … but not today.  
Thank you!

---

<div class="post-metadata">

### Author: ![sdanisch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sdanisch/32/1406_2.png) [@sdanisch](https://discourse.julialang.org/u/sdanisch)
#### Post date: [March 4, 2020, 9:46pm UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/6 "2020-03-04T21:46:17Z")

</div>

```julia
function juliaset(z0, c, maxiter)
    z = z0
    for i in 1:maxiter
        abs2(z) > 4f0 && return (i - 1) % UInt8
        z = z * z + c
    end
    return maxiter % UInt8 # % is used to convert without overflow check
end

function threaded(output, input, c)
    Threads.@threads for i in eachindex(input)
        @inbounds output[i] = juliaset(input[i], c, 255)
    end
    return output
end
# scale should change with picture size
scale = 200.0 # the greater the more details
width, height = 800, 700
x = -0.5; y = 1.2
c = x+y*im
xvalues = LinRange(-width/2, width/2, width) ./ scale
yvalues = LinRange(-height/2, height/2, height) ./ scale
inputmatrix = xvalues' .+ yvalues .* im 
result = fill(UInt(8), height, width)

@time threaded(result, inputmatrix, c)

```

That should be faster… You could make it even faster with an inner loop, that can be SIMD accelerated

---

<div class="post-metadata">

### Author: ![Skoffer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/skoffer/32/378_2.png) [@Skoffer](https://discourse.julialang.org/u/Skoffer)
#### Post date: [March 5, 2020, 5:02am UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/7 "2020-03-05T05:02:25Z")

</div>

There is also GPU implementation of Mandelbrot set, which can easily be adapted to your needs [Coding the Mandelbrot Set in Julia (with GPU) - YouTube](https://youtu.be/xVLxTk3SqsA)

---

<div class="post-metadata">

### Author: ![zenon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zenon/32/6508_2.png) [@zenon](https://discourse.julialang.org/u/zenon)
#### Post date: [March 5, 2020, 8:35am UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/8 "2020-03-05T08:35:29Z")

</div>

**Wrong:** Using your version of juliaset(…) immediately was actually slower, but @code\_warntype pointed out that in z = z0 and thus in abs2(z) the type was assumed to be Any.  
_I take this paragraph back. Tried to reproduce this with a fresh REPL, and it was fast now. Sorry._

Using your function as function juliaset(z0 :: Complex{Float64}, c, maxiter) is three times faster than mine. This is cool!

Thank you!

---

<div class="post-metadata">

### Author: ![sdanisch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sdanisch/32/1406_2.png) [@sdanisch](https://discourse.julialang.org/u/sdanisch)
#### Post date: [March 5, 2020, 10:17am UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/9 "2020-03-05T10:17:10Z")

</div>

> [@zenon](#):
>
> is three times faster than mine. This is cool!

Only 3 times?  
I get:

```julia
@btime juliaSet($batches, $valueGrid, $dataGrid, $c)
51ms
@btime threaded($result, $inputmatrix, $c)
729.403 μs

```

Which is a speed up of ~70x

---

<div class="post-metadata">

### Author: ![sdanisch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sdanisch/32/1406_2.png) [@sdanisch](https://discourse.julialang.org/u/sdanisch)
#### Post date: [March 5, 2020, 10:20am UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/10 "2020-03-05T10:20:48Z")

</div>

> [@zenon](#):
>
> 8 core Ryzen with 15 threads,

To what number did you set `JULIA_NUM_THREADS` before running Julia?  
And are you using `julia -O3` ?

---

<div class="post-metadata">

### Author: ![zenon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zenon/32/6508_2.png) [@zenon](https://discourse.julialang.org/u/zenon)
#### Post date: [March 5, 2020, 12:35pm UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/11 "2020-03-05T12:35:42Z")

</div>

I’m not at that computer in the moment, but for Thread.nthreads() I got 16.

Shouldn’t that be enough? (I did it in Juno.)

(Until now I only played with your version of the inner loop, as I don’t have an interesting amount of threads on my current machine.)

---

<div class="post-metadata">

### Author: ![sdanisch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sdanisch/32/1406_2.png) [@sdanisch](https://discourse.julialang.org/u/sdanisch)
#### Post date: [March 5, 2020, 1:47pm UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/12 "2020-03-05T13:47:04Z")

</div>

Alright, the inner version shouldnt be that different performance wise!

 ![image](https://global.discourse-cdn.com/julialang/original/3X/f/4/f4a692eadad54245d16ae9ae93b27afeec638a2a.png)  
Make sure you have 3 for optimization level 😉  
Might not change anything, but I remember that 03 pretty much only enabled better optimizations for reducing over a variable in a loop, which we are actually doing here…

---

<div class="post-metadata">

### Author: ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)
#### Post date: [March 5, 2020, 1:52pm UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/13 "2020-03-05T13:52:22Z")

</div>

> [@sdanisch](#):
>
> Might not change anything, but I remember that 03 pretty much only enabled better optimizations for reducing over a variable in a loop, which we are actually doing here…

I don’t think -O3 does really anything right now. At least I haven’t seen a single example where it is faster since SLP got enabled with -O2.

---

<div class="post-metadata">

### Author: ![sdanisch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sdanisch/32/1406_2.png) [@sdanisch](https://discourse.julialang.org/u/sdanisch)
#### Post date: [March 5, 2020, 1:57pm UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/14 "2020-03-05T13:57:20Z")

</div>

Yeah I thought almost nothing changes with -O3 but this one thing… but my information is pretty old 😉

---

<div class="post-metadata">

### Author: ![zenon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zenon/32/6508_2.png) [@zenon](https://discourse.julialang.org/u/zenon)
#### Post date: [March 5, 2020, 9:36pm UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/15 "2020-03-05T21:36:15Z")

</div>

I come to the conclusion that I don’t know yet what I’m doing; getting results all over the place.

So finally updated the gist again. [Gist](https://gist.github.com/zenon/88e30f7894bf38fcc161699a1760dc22)  
j(…) is now a comparing test, calling (at)btimes for both our code versions.

simon:  
12.790 ms (116 allocations: 13.72 KiB)  
zenon:  
34.274 ms (2085420 allocations: 57.48 MiB)

Well this is AFTER I changed my code:

1. corrected UInt to a concrete UInt32 (change to 32 from 8, seems to do no harm) in my inner loop.
2. replaced z^2 by z\*z (from your code), inner loop
3. replaced abs by abs2, which seems to be much faster for Complex{Float64} (from your code), inner loop  
4.used (at)inbounds (from your code), but that seemed not to change anything.

So. Many thanks.  
I have to leave the large computer again, for 3 days. That was fun!

Kind greetings, z.

---

<div class="post-metadata">

### Author: ![c42f](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/c42f/32/52842_2.png) [@c42f](https://discourse.julialang.org/u/c42f)
#### Post date: [March 6, 2020, 3:14am UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/16 "2020-03-06T03:14:04Z")

</div>

Having a quick look at the code, a couple of thoughts:

- `batchify` could be replaced by `Iterators.partition`? But it also should be unnecessary because `@threads` already statically partitions the work into `nthreads` batches, I think.
- In `juliaSet` you use `for y = 1:width`. This likely kills performance because `width` is a global variable. You can use `size(dataGrid,2)` instead.

---

<div class="post-metadata">

### Author: ![zenon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zenon/32/6508_2.png) [@zenon](https://discourse.julialang.org/u/zenon)
#### Post date: [March 6, 2020, 5:58am UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/17 "2020-03-06T05:58:46Z")

</div>

Ah, thank you!  
I assumed there must be sth like batchify already, I just didn’t know where to find it 🙂

The global vars are gone in rev. 2 and 3 of the gist.

---

<div class="post-metadata">

### Author: ![c42f](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/c42f/32/52842_2.png) [@c42f](https://discourse.julialang.org/u/c42f)
#### Post date: [March 6, 2020, 6:26am UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/18 "2020-03-06T06:26:08Z")

</div>

In the latest version I can see, there’s still the use of global `width` in:

```julia
function juliaSet(batches, valueGrid, dataGrid, c)
    @threads for batch in batches
        for x in batch
            for y = 1:width
                @inbounds dataGrid[x,y] = julia(valueGrid[x,y], c, MAXITER)
            end
        end
    end
end

```

---

<div class="post-metadata">

### Author: ![zenon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zenon/32/6508_2.png) [@zenon](https://discourse.julialang.org/u/zenon)
#### Post date: [March 6, 2020, 6:40am UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/19 "2020-03-06T06:40:37Z")

</div>

Uuups. Thank you!, escaped me.

---

<div class="post-metadata">

### Author: ![mschauer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mschauer/32/13946_2.png) [@mschauer](https://discourse.julialang.org/u/mschauer)
#### Post date: [March 6, 2020, 6:43am UTC](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533/20 "2020-03-06T06:43:55Z")

</div>

If you are doing Julia sets in Julia you might be tempted to replace `z*z` by `z*z*z`…

[https://github.com/JuliaLang/julia/issues/16357#issuecomment-219693645](https://github.com/JuliaLang/julia/issues/16357#issuecomment-219693645)

[Next page](https://discourse.julialang.org/t/julia-sets-in-julia-using-interact-jl-id-like-to-get-it-faster/35533.md?page=2)
