Hello, and welcome to C++ Weekly. I am your host Jason Turner. I am available for code reviews and
on-site training. Now in this episode I want to talk about the parallel algorithms which we
haven't really talked about at all, but in the right scenario they're magic, so stay tuned!
So what I have here is a simple Mandelbrot renderer, and I've done this in several
languages. Now it's one of the things that I like to do when I'm playing with a new
tool is make a Mandelbrot renderer. This one is, well, it's pretty simple
and just so you know, yes this is in fact available up on the official C++ Weekly
GitHub project; and I have this absolutely terrible little make Mandelbrot script here
and some of these things are completely useless I'm sure. I don't have a CMake file for it,
it should be updated. There's so many things that are wrong with this code,
but it was a fun little project at one time that I have pulled forward through a couple of different
iterations and C++ things. So I'm just going to go ahead and run this make_Mandelbrot script
and this is clearly specific to my system here. You'll notice I'm getting this
TBB error and that's because I'm including the parallel algorithms header. This is a very simple
single threaded Mandelbrot renderer and I'm going to run it here, and I have really no idea how
what kind of performance I expect to get with the recording going on here, but this max
iterations rendered in 1.99 seconds. Here, this is the key thing that we're going to pay attention to
and that is how long it takes to reach the max iterations. Now for the sake of feedback
I render a single frame of this at some nominal number of iterations. Now the way Mandelbrot works
is you keep iterating on a value until you decide that you have reached, you know, some
endpoint or whatever, so certain parts of the Mandelbrot set are actually faster to render
or slower to render than other parts of the Mandelbrot set. So it has to, you know, we want
visible feedback as quickly as possible, so I do some amount of iterations, and even if I haven't
hit the end point then I just display that to the screen; and here I've zoomed in on this
particular portion of the Mandelbrot set that I like and we can see it's taking about 1.9 seconds
to finally decide, "Okay, I have gone as deep into this as I can. " Again, I'm recording
and rendering on the same computer so I'm just gonna back out a little bit but I expect that
we're gonna see, you know, there we go this this part of the set is one second. You can, you know,
move around and whatever with the arrow keys and if you hold down shift then you get the faster
increments into these things; and I'm using page up and page down to zoom in and zoom out on it.
The window's not resizable it's all very simply done in SFML, but this is kind of slow. Mandelbrot
is what is known as trivially parallelizable, that is each pixel can be calculated independently
without any other input from any other surrounding thing, so let's go ahead and parallelize this.
Now what I have in here (this is my transform function) so what I have is the set of pixels
that I want to calculate, and I just have this function called get_color.
get_color takes the point and it needs other information about how it's rendering it - what
is the current center that we are trying to render at, how many iterations do we want to try to go,
and this is all complex numbers, so I'm using std::complex here, and I haven't covered this
standard complex at all on C++ Weekly. It's actually on my to-do list to do an
episode about it specifically. So while iteration is less than stop_iteration it's just going to go
through and keep trying to like, refine the point. It's going to do as many calculations,
as many iterations, as it can until it finds some interesting value there,
and I've got these other modes like absolute value, and I can change which power
of the Mandelbrot set I want to render and that kind of thing, and you know, this code is not
great. This code could definitely be improved upon. There's no question in my mind
that it could be improved upon, but let's go ahead and get back to this transform.
So this transform takes a set of input points and creates a set of output points and you can see
this entire interface should be redesigned. It's not really safe. I'm doing things that are not
quite thread safe in how I get that image buffer back to be displayed. So that's probably bad, but
I'm not going to worry about it at the moment. I'm going to focus on this the actual calculation work
happening here. So I've got standard transform, from the begin to the end, and then I say I want
to call this thing get_color, and now notice I am intentionally copying in all of the settings data
because I want to make sure that each call here is getting its own copy of the data. There's no
chance of sharing or whatever. That would be bad in the cases where we can limit it most easily.
Now in C++ 17 we got parallel algorithms and GCC works with these parallel algorithms.
Now I'm telling the standard library that I can run parallel unsequenced. I don't care what order
any of these pixels are calculated in, or how many parallel runs are made, so it's completely up to
the compiler here. If it can - in the runtime - if it can parallelize this I'm telling it it can, and
you can trust me. Let's go ahead and pull this up. Now I'm going to do two things - I'm going to pull
up htop so we can see like, right now my system is mostly idle, and I've got eight cores available.
Now I'm going to go ahead and rerun with this version with the execution pair on sequence,
and now keeping in mind these kinds of numbers that we saw 1.79, 1.99, 1.89 and this has two
threads running. One is just, there's the main thread, and then there's another thread that's
running just to update the display whenever it can. All right, so we've recompiled with this
parallel unsequenced, and let's go ahead and run this again, and you may have noticed how quickly
it came up. I'm going to go back over here so that we can actually see what htop is telling us.
Well, you know, we have to switch back and forth between these things, but we can immediately
see that I'm getting 0.3 seconds. I'm getting pretty much perfect parallelization of my code
by changing that one little flag. And now zooming in, I can see just how much faster it can zoom in
and that I'm maxing out my system here trying to do this rendering. I can let go
and let it finish doing its calculations here.
Yeah, so that finished in 0.13 seconds. We can still zoom right in on the set
that we know is about 1.9 seconds to render in these particular coordinates
and watch and wait. So it's just iterating over these things trying to refine the details as much
as possible while giving us some feedback and we can see m two threads here, the main thread and
the thread that's looking to say, "Hey is there a new image for me display? Hey is there a new
image for me display?" This code could definitely be better, but yeah about 0.32 seconds here so a
quite you know, usable, little Mandelbrot renderer and browser if you're into this kind of thing.
I always find Mandelbrot to be fun and of course pretty pictures.
Right, let's see which one of these I end up using for the thumbnail for this episode
because it's so rare on C++ Weekly that I actually have some pretty thumbnail.
Having all eight cores maxed out does look nice.
That's a nice little area, "happy clouds," that kind of thing,
so you know, thanks for watching this episode of C++ Weekly. Be aware of the parallel algorithms.
All of the algorithms in the standard library that can be parallelized are available in Visual Studio
and GCC's standard library implementation. It is not yet available in llvm's C++
standard library implementation unfortunately, so that limits the portability of your code
but, um man, you know what, you got the right test case, use case, this is this is absolutely amazing
right? Like to be able to just get instantly faster code in the case where you've got something
that's trivially parallelizable, and you know, certainly not everything but a lot of problems
are. You can see where I reach the limits of my precision down in that little corner there, but
yeah, check this stuff out and thanks for watching this episode of C++ Weekly! Be sure to subscribe.
you