Stream: show and tell

Topic: Plotting Fractals


view this post on Zulip Bryce Miller (Sep 16 2022 at 21:11):

I plotted the mandelbrot set using Roc Screen-Shot-2022-09-16-at-5.10.32-PM.png

view this post on Zulip Bryce Miller (Sep 16 2022 at 21:12):

Would be very curious to do a performance comparison with other languages. I'm using the dumbest escape time algorithm possible.

view this post on Zulip Richard Feldman (Sep 16 2022 at 21:29):

cooooooooool!!!

view this post on Zulip Brian Carroll (Sep 16 2022 at 21:29):

Nice!

view this post on Zulip Bryce Miller (Sep 16 2022 at 21:29):

I really want to try doing this properly perhaps using that Bevy platform @JanCVanB started working on.

view this post on Zulip jan kili (Sep 16 2022 at 22:10):

Do you mean this Plotters platform? https://github.com/JanCVanB/roc-plotters

view this post on Zulip jan kili (Sep 16 2022 at 22:10):

I'm currently updating it to work with latest Roc, so this is good timing

view this post on Zulip jan kili (Sep 16 2022 at 22:11):

(huh, why did I name the Bevy one "bevies", plural? :thinking: foolish https://github.com/JanCVanB/roc-bevies)

view this post on Zulip jan kili (Sep 16 2022 at 22:12):

:point_of_information: :surprise: Whoa, that ascii mandelbrot is PRETTY, great job!

view this post on Zulip Bryce Miller (Sep 16 2022 at 22:25):

Thanks. I had to set the complex plane to stretch to fit the plot, and ensure that the plot was exactly three times as wide as it is tall.

view this post on Zulip Bryce Miller (Sep 16 2022 at 22:30):

I saw the roc-plotters platform, but when I saw the line graphs I figured it might not be a good fit for plotting fractals. Maybe I was wrong about that tho! I figured the Bevy platform used for the breakout example would be a way to render actual pixels.

I don't know Bevy at all but if it provided access to the GPU that would really unlock some performance. These plots are float-heavy.

view this post on Zulip Bryce Miller (Sep 16 2022 at 22:33):

When I get some time, I'd like to do ASCII versions in JS, Rust, and Haskell too. I'm extremely curious how Roc would compare considering how it did with quicksort.

view this post on Zulip Bryce Miller (Sep 16 2022 at 22:37):

I doubt I am smart enough to do it, but it would be interesting to see how close we could get to Kalles Fraktaler with Roc

view this post on Zulip Bryce Miller (Sep 16 2022 at 22:38):

But yeah if you think the plotters platform will work for this once you have it updated, I'll definitely do a version with that!

view this post on Zulip jan kili (Sep 16 2022 at 22:41):

Take a look at the scatter example, it hints at how the Plotters lib can do WAY more than I integrated with

view this post on Zulip jan kili (Sep 16 2022 at 22:46):

And the Plotters rust library in general - it might be simpler to extend that platform than bevy, but only if you have no bevy experience like I don't

view this post on Zulip jan kili (Sep 16 2022 at 22:46):

Either way, help advancing any aspect of those visualization platforms is very welcome!!

view this post on Zulip jan kili (Sep 16 2022 at 22:47):

Good point about the GPU...

view this post on Zulip jan kili (Sep 16 2022 at 22:47):

:shrug:

view this post on Zulip jan kili (Sep 17 2022 at 01:31):

@ me with any questions or ideas! I'm happy to review code, contribute, etc

view this post on Zulip Bryce Miller (Sep 17 2022 at 02:07):

Sweet!

view this post on Zulip Bryce Miller (Sep 17 2022 at 19:03):

Update: created a repo and added a couple additional implementations in Rust and JS. https://github.com/sandprickle/fROCtals

view this post on Zulip Bryce Miller (Sep 17 2022 at 19:09):

Set up all three implementations to create identical plots, with max iterations set to 1M.
On my M1 Macbook Air, I got the following results:

JS (Node v16): ~10.3 s
Roc: ~ 4.62 s
Rust: ~ 4.57 s

So my question is: Is Roc just that fast, or did I fail to optimize the Rust version properly?

view this post on Zulip Folkert de Vries (Sep 17 2022 at 19:17):

doesn't look like anything is obviously inefficient in the rust code

view this post on Zulip Folkert de Vries (Sep 17 2022 at 19:17):

so if you ran a release build then yes

view this post on Zulip Folkert de Vries (Sep 17 2022 at 19:18):

the code here is very simple, so roc and rust should produce pretty much the same thing (because both use llvm)

view this post on Zulip Bryce Miller (Sep 17 2022 at 19:22):

Cool. Would you consider this to be an algorithm that would typically be pathological for a pure functional language, or not so much?

IOW, is this a good showcase of the optimizations you’ve done with the Roc compiler?

view this post on Zulip Folkert de Vries (Sep 17 2022 at 19:29):

not in terms of performance I think? but the sort of "textbook implementation" is easier in roc I think

view this post on Zulip Folkert de Vries (Sep 17 2022 at 19:30):

I'd be interested to see how haskell fares, in particular when you don't try too hard to optimize

view this post on Zulip Bryce Miller (Sep 17 2022 at 19:33):

I was thinking I’d do one in Haskell next

view this post on Zulip Bryce Miller (Sep 17 2022 at 19:34):

Do you think something along the lines of my Roc implementation would be sufficiently naive for the Haskell implementation?

view this post on Zulip Folkert de Vries (Sep 17 2022 at 19:34):

yes I think that would be interesting

view this post on Zulip Bryce Miller (Sep 17 2022 at 19:34):

Cool. That’s next on my list then.

view this post on Zulip Folkert de Vries (Sep 17 2022 at 19:35):

in particular: we see how ghc does versus llvm on this problem, and how good/bad the roc stdlib is versus the haskell one

view this post on Zulip Bryce Miller (Sep 17 2022 at 19:41):

I made it to chapter 4 or so of Graham Hutton’s Haskell book, and that was probably a year ago or more. We’ll see how far I get with it.

view this post on Zulip Bryce Miller (Sep 17 2022 at 19:41):

So standard prelude is what you’re more curious about? That’s what Haskell calls their stdlib right?

view this post on Zulip Folkert de Vries (Sep 17 2022 at 19:41):

yeah

view this post on Zulip Bryce Miller (Sep 17 2022 at 19:42):

Cool, I don’t know how to use alternate preludes anyway 😅

view this post on Zulip Folkert de Vries (Sep 17 2022 at 19:42):

one advantage that the more modern preludes may have is that they use Text instead of String (i.e. a linked list of characters, a bad idea)

view this post on Zulip Folkert de Vries (Sep 17 2022 at 19:43):

for the rest of the code, I don't think a more modern prelude would change anything

view this post on Zulip Folkert de Vries (Sep 17 2022 at 19:43):

but haskell does have mutable vectors in the prelude somewhere, which might be an improvement, but those are so obscure that I'd say it doesn't really count as idiomatic haskell

view this post on Zulip Bryce Miller (Sep 17 2022 at 19:44):

I was about to ask whether I should use the default list or one of the vectors.

view this post on Zulip Bryce Miller (Sep 17 2022 at 19:44):

I’m curious to try both.

view this post on Zulip Bryce Miller (Sep 17 2022 at 19:46):

Using a list puts idiomatic Haskell against idiomatic Roc, while using a vector would highlight more of the overhead/optimization differences when using the same data structures.

view this post on Zulip Bryce Miller (Sep 17 2022 at 19:47):

Or similar, I guess. Not sure if haskell vectors would be on the stack like the arrays I used in the Rust version.

view this post on Zulip Folkert de Vries (Sep 17 2022 at 19:53):

I don't think they would be

view this post on Zulip Richard Feldman (Sep 17 2022 at 21:23):

@Bryce Miller that's a very cool result! Would you mind if I share a screenshot of your post with the numbers?

view this post on Zulip Bryce Miller (Sep 17 2022 at 21:25):

@Richard Feldman Don't mind at all! I also have some results from my desktop (Ryzen 3800X) which shows a larger difference between Roc and Rust.

view this post on Zulip Bryce Miller (Sep 17 2022 at 21:28):

3800X results (Fedora)
JS: ~ 9 s (not super consistent)
Roc: ~ 3.16 s
Rust: ~ 2.86 s

view this post on Zulip Bryce Miller (Sep 17 2022 at 21:29):

Odd that JS didn't get much faster :thinking:

view this post on Zulip jan kili (Sep 17 2022 at 21:47):

How was/is your initial/current developer experience? Docs, syntax, compiler, etc

view this post on Zulip Richard Feldman (Sep 17 2022 at 21:57):

@Bryce Miller I bet the gap will be much bigger if you do like 1000 elements, because then the jit wouldn't kick in

view this post on Zulip Richard Feldman (Sep 17 2022 at 21:58):

(the gap between JS and the others, I mean - Roc doesn't use a JIT)

view this post on Zulip Bryce Miller (Sep 17 2022 at 22:34):

@Richard Feldman You mean using a smaller array, or fewer iterations, or both? Maybe I can add a cli arg to all the implementations to set the iteration count.

view this post on Zulip Bryce Miller (Sep 17 2022 at 22:41):

@JanCVanB the biggest thing I wanted was hover definitions so I could check type signatures. Format on save would be really nice, but at least Roc has a built-in formatter. I’m still fighting to get a haskell formatter working with nvim. Coffeescript syntax highlighting kinda breaks with backpassing, so I switched to the VScode extension that someone made. Less robust, but at least I don’t get a lot of red with backpassed functions.

Speaking of backpassing, that’s going to take some getting used to. Coming from Elm, a lot of the other characteristics of the language are pretty intuitive. Backpassing syntax still hurts my brain a little bit.

view this post on Zulip Folkert de Vries (Sep 17 2022 at 22:43):

you don't need to use it, especially for the list stuff

view this post on Zulip Bryce Miller (Sep 17 2022 at 22:44):

Yeah, I don't feel like this little script really needed it. I included it in one place just to try getting used to the syntax. Once my brain is able to decode it quickly, I think it will be a very welcome feature. Indentation hell is real.

view this post on Zulip Bryce Miller (Sep 17 2022 at 22:46):

On that note, really grateful that local values don't have an additional level of indentation like they do in Elm's let...in.

view this post on Zulip Bryce Miller (Sep 17 2022 at 22:47):

Oh, and docs were pretty good. The function signature for List.mapWithIndex I think could use a set of parens for the callback tho?

view this post on Zulip Richard Feldman (Sep 17 2022 at 22:47):

@Bryce Miller I meant smaller number of iterations - like 1000 instead of 1M

view this post on Zulip Folkert de Vries (Sep 17 2022 at 22:48):

wow a time-traveling message

view this post on Zulip Bryce Miller (Sep 17 2022 at 22:49):

Might be useful to have a link to roc-lang.org/builtins tutorial though? I had to rummage through Zulip to find it.

view this post on Zulip Richard Feldman (Sep 17 2022 at 22:49):

yeah that was weird haha, not sure why it repeated much later

view this post on Zulip Richard Feldman (Sep 17 2022 at 22:49):

@Bryce Miller that's a good call! I can add a link to it in the readme

view this post on Zulip Bryce Miller (Sep 17 2022 at 22:49):

you've heard of time-traveling debuggers. now get ready for... time traveling zulip chats!

view this post on Zulip Bryce Miller (Sep 17 2022 at 22:50):

I'll do some runs with 1k iterations and see what happens!

view this post on Zulip Folkert de Vries (Sep 17 2022 at 22:53):

btw hyperfine is an excellent tool for running benchmarks like this

view this post on Zulip Folkert de Vries (Sep 17 2022 at 22:54):

don't want to be doing eyeball statistics

view this post on Zulip Bryce Miller (Sep 17 2022 at 23:05):

I think I was looking at user time only, not total time for the numbers I provided above (using the time command I think built in to zsh?). I'll definitely re-run these benchmarks using hyperfine. Thanks for the suggestion!

Looking at total time using the time command, 1k iterations makes JS roughly an order of magnitude slower. Roc and Rust are hovering around 0.005s, where JS is around 0.045s.

view this post on Zulip Brendan Hansknecht (Sep 17 2022 at 23:53):

I wonder how much of that is js engine startup time and such.

view this post on Zulip Brendan Hansknecht (Sep 17 2022 at 23:55):

Like how long does Js take with 0 and 1 loop iterations?

view this post on Zulip Bryce Miller (Sep 18 2022 at 02:06):

Added some benchmarks via hyperfine to the repo.

view this post on Zulip Bryce Miller (Sep 18 2022 at 02:08):

I’ll try to do some with 1 iteration too. I mainly included JS as a reference “slow” language.

view this post on Zulip Brendan Hansknecht (Sep 18 2022 at 02:11):

Awesome. Interested to see the results. Can make some pretty graphs as the number of iterations increase

view this post on Zulip Bryce Miller (Sep 18 2022 at 02:23):

I have the Haskell implementation nearly done too. Either I messed up the algorithm or I’m making some rookie Haskell mistakes. Or both.

view this post on Zulip Bryce Miller (Sep 18 2022 at 17:45):

Screen-Shot-2022-09-18-at-1.44.25-PM.png Apple M1 results with 1K iterations including Haskell, via hyperfine

view this post on Zulip Bryce Miller (Sep 18 2022 at 17:46):

Screen-Shot-2022-09-18-at-1.45.59-PM.png AMD Ryzen 3800X results including Haskell, via hyperfine

view this post on Zulip Bryce Miller (Sep 18 2022 at 17:47):

Using default Prelude for Haskell, no vectors. Completely unoptimized to the best of my knowledge.

view this post on Zulip Bryce Miller (Sep 18 2022 at 17:48):

Next up is to accept cli param for iteration count so I can use hyperfine to generate data

view this post on Zulip Brendan Hansknecht (Sep 18 2022 at 17:57):

From a random post online about Haskel:

There is no such thing as "release mode". Perhaps you want to enable more optimization? In that case, pass in --ghc-options -O2. Alternatively, add -O2 to ghc-options: in your cabal file.

view this post on Zulip Bryce Miller (Sep 18 2022 at 18:16):

Oh interesting. I was wondering whether there was a flag I should be using for a production build. I know almost nothing about Haskell.

view this post on Zulip Bryce Miller (Sep 18 2022 at 18:20):

Ok that's actually a substantial improvement.

view this post on Zulip Bryce Miller (Sep 18 2022 at 18:23):

Screen-Shot-2022-09-18-at-2.22.50-PM.png Apple M1 with -O2 GHC option

view this post on Zulip Brendan Hansknecht (Sep 18 2022 at 18:26):

Oh wow, about 2x faster

view this post on Zulip Bryce Miller (Sep 18 2022 at 18:27):

Yeah pretty surprising.

view this post on Zulip Bryce Miller (Sep 18 2022 at 18:29):

Would a substantially larger plot hurt the linked lists in Haskell more? Maybe I can do some supersampling if the larger memory footprint would reveal something interesting.

view this post on Zulip Brendan Hansknecht (Sep 18 2022 at 19:05):

I depends on the algorithm. Haven't actually read through your code, but i wouldn't expect any huge slowdown from larger lists

view this post on Zulip Brendan Hansknecht (Sep 18 2022 at 19:05):

But again, heavily depends on how the lists are being used


Last updated: Jul 06 2025 at 12:14 UTC