-
Notifications
You must be signed in to change notification settings - Fork 263
Profiling with VisualVM
There's an excellent guide on VisualVM here. If you read it start to finish, you'll know more than the guy writing this wiki.
VisualVM might already be on you computer in the 'bin' folder of your java installation. If not, download it here. Run VisualVM:
While VisualVM is open, run the GUI. You must run the GUI from processing for profiling to work.
A new entry will appear named "processing.core.PApplet". Double-click it to open a new tab for the GUI process:
Set up the GUI in synthetic mode, 60FPS, with the Time Series and HeadPlot widgets open. Then let it sit idle. The GUI takes up close to 20% of cpu cycles on an i7-7700HQ. Why is it so resource intensive when it's just sitting idle?
First, let's figure out why time series is so busy while idle.
- Set up the GUI in Synthetic Mode, 16 channels.
- Set the framerate to 60fps
- Choose a single-pane layout and select the Time Series widget:
Go under the Sampler
tab and hit the CPU
button.
At this point, we want to perform the action that we are profiling for. We are profiling the GUI sitting idle, so we don't have to do anything. Just let it run for a few seconds... Then hit Stop
.
Once that's done, we can start digging to find the GUI's update() and draw() functions. You'll see a list of threads, the main gui thread is main-FPSAWTAnimator...
:
We found the GUI draw() entry point. Let's dig a little further to find the culprit of our high CPU usage.
There it is! The Gplot.drawLines()
function is taking the bulk of our CPU time. Specifically, this is each ChannelBar
instance's draw() function calling Gplot.drawLines()
. Specifically, this is W_TimeSeries.pde
line 557, as of writing:
So how to we fix this? Well, the GPlot class might be inefficient beyond our control. However, maybe we can skip calling drawLines()
if the plot data hasn't changed.
Let's do the same thing with the Head Plot widget:
- Set up the GUI in Synthetic Mode, 16 channels.
- Set the framerate to 60fps
- Choose a single-pane layout and select the Head Plot widget.
Sitting idle, the headplot widget is taking up 15% of cpu cycles on an i7-7700HQ.
Let's run CPU sampling for a few seconds and start digging:
Well, this is interesting! The bulk of the time is taken up by updateHeadVoltages
(W_HeadPlot.pde, line 1028 as of writing).
This is a function with a nested for loop (for each pixel) that runs on every update, and does expensive calculations per pixel.
Well for starters, we can be lazy. We probably don't need to recompute the head voltages if the data is not changing.
Q: Okay, but what if we are streaming and the values are constantly changing? Then how to we make it faster?
Well, maybe we don't need to re-do the calculation for every pixel. Maybe we know for sure that some pixels aren't going to change, and we can skip them.
Aside from that, we might want to look into caching. That is, we look for any duplicate work, and we save the caculations in memory (the cache). We are trading CPU time for memory.
Maybe the math code is inefficient and can be made better?
Q: Let's say we've optimized the code as much as we can, but it' still slow. On slower machines, it's causing the framerate to drop. Then what?
If it's causing the framerate to drop, and there is no other way to optimize, then, and only then, consider running the code on a separate thread. Running on a separate thread will not make the CPU usage drop. It will just move those computations to another "process" within the GUI (i.e., thread). Also, threading introduces complexity, and we need to be careful not to accidentally read head voltages while they are being written. We need to properly synchronize the threads.
updateHeadVoltages()
might be taking the bulk of the time, but convertVoltagesToHeadImage()
is taking a pretty big chunk too. Specifically, the calcPixelColor()
function it calls. Speifically, the color()
constructor used by that function.
This is a hint that we are spending a lot of time allocation new Color() objects. Perhaps we can edit the pixel colors in place instead of creating a new color object and replacing the old one?
Please feel free to contribute tips, tricks and best practices.