I plotted the mandelbrot set using Roc Screen-Shot-2022-09-16-at-5.10.32-PM.png
Would be very curious to do a performance comparison with other languages. I'm using the dumbest escape time algorithm possible.
cooooooooool!!!
Nice!
I really want to try doing this properly perhaps using that Bevy platform @JanCVanB started working on.
Do you mean this Plotters platform? https://github.com/JanCVanB/roc-plotters
I'm currently updating it to work with latest Roc, so this is good timing
(huh, why did I name the Bevy one "bevies", plural? :thinking: foolish https://github.com/JanCVanB/roc-bevies)
:point_of_information: :surprise: Whoa, that ascii mandelbrot is PRETTY, great job!
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.
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.
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.
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
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!
Take a look at the scatter example, it hints at how the Plotters lib can do WAY more than I integrated with
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
Either way, help advancing any aspect of those visualization platforms is very welcome!!
Good point about the GPU...
:shrug:
@ me with any questions or ideas! I'm happy to review code, contribute, etc
Sweet!
Update: created a repo and added a couple additional implementations in Rust and JS. https://github.com/sandprickle/fROCtals
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?
doesn't look like anything is obviously inefficient in the rust code
so if you ran a release build then yes
the code here is very simple, so roc and rust should produce pretty much the same thing (because both use llvm)
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?
not in terms of performance I think? but the sort of "textbook implementation" is easier in roc I think
I'd be interested to see how haskell fares, in particular when you don't try too hard to optimize
I was thinking I’d do one in Haskell next
Do you think something along the lines of my Roc implementation would be sufficiently naive for the Haskell implementation?
yes I think that would be interesting
Cool. That’s next on my list then.
in particular: we see how ghc does versus llvm on this problem, and how good/bad the roc stdlib is versus the haskell one
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.
So standard prelude is what you’re more curious about? That’s what Haskell calls their stdlib right?
yeah
Cool, I don’t know how to use alternate preludes anyway 😅
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)
for the rest of the code, I don't think a more modern prelude would change anything
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
I was about to ask whether I should use the default list or one of the vectors.
I’m curious to try both.
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.
Or similar, I guess. Not sure if haskell vectors would be on the stack like the arrays I used in the Rust version.
I don't think they would be
@Bryce Miller that's a very cool result! Would you mind if I share a screenshot of your post with the numbers?
@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.
3800X results (Fedora)
JS: ~ 9 s (not super consistent)
Roc: ~ 3.16 s
Rust: ~ 2.86 s
Odd that JS didn't get much faster :thinking:
How was/is your initial/current developer experience? Docs, syntax, compiler, etc
@Bryce Miller I bet the gap will be much bigger if you do like 1000 elements, because then the jit wouldn't kick in
(the gap between JS and the others, I mean - Roc doesn't use a JIT)
@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.
@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.
you don't need to use it, especially for the list stuff
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.
On that note, really grateful that local values don't have an additional level of indentation like they do in Elm's let...in
.
Oh, and docs were pretty good. The function signature for List.mapWithIndex I think could use a set of parens for the callback tho?
@Bryce Miller I meant smaller number of iterations - like 1000 instead of 1M
wow a time-traveling message
Might be useful to have a link to roc-lang.org/builtins tutorial though? I had to rummage through Zulip to find it.
yeah that was weird haha, not sure why it repeated much later
@Bryce Miller that's a good call! I can add a link to it in the readme
you've heard of time-traveling debuggers. now get ready for... time traveling zulip chats!
I'll do some runs with 1k iterations and see what happens!
btw hyperfine
is an excellent tool for running benchmarks like this
don't want to be doing eyeball statistics
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.
I wonder how much of that is js engine startup time and such.
Like how long does Js take with 0 and 1 loop iterations?
Added some benchmarks via hyperfine to the repo.
I’ll try to do some with 1 iteration too. I mainly included JS as a reference “slow” language.
Awesome. Interested to see the results. Can make some pretty graphs as the number of iterations increase
I have the Haskell implementation nearly done too. Either I messed up the algorithm or I’m making some rookie Haskell mistakes. Or both.
Screen-Shot-2022-09-18-at-1.44.25-PM.png Apple M1 results with 1K iterations including Haskell, via hyperfine
Screen-Shot-2022-09-18-at-1.45.59-PM.png AMD Ryzen 3800X results including Haskell, via hyperfine
Using default Prelude for Haskell, no vectors. Completely unoptimized to the best of my knowledge.
Next up is to accept cli param for iteration count so I can use hyperfine to generate data
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.
Oh interesting. I was wondering whether there was a flag I should be using for a production build. I know almost nothing about Haskell.
Ok that's actually a substantial improvement.
Screen-Shot-2022-09-18-at-2.22.50-PM.png Apple M1 with -O2
GHC option
Oh wow, about 2x faster
Yeah pretty surprising.
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.
I depends on the algorithm. Haven't actually read through your code, but i wouldn't expect any huge slowdown from larger lists
But again, heavily depends on how the lists are being used
Last updated: Jul 06 2025 at 12:14 UTC