Often during simulation model building, one comes to a point where you need to optimize your model's performance. Either through improved memory usage or CPU utilization. This can be a near-impossible task through just a code review or manual trial and error changes. Luckily, a specific tool is available for Java applications to help you and make life easier.
This is part 2 of a 2 part series where we look at using a profiler to analyze model performance. Part 1 focussed on improving memory consumption and how you can detect memory leaks - read more about it here.
In this post, we will look at an example of how to analyze and improve CPU performance.
Rather watch the 8-minute video? Click here
Example
Let's look at the following super simple simulation model one of our interns developed as a starting point for a much bigger simulation.
We have a population of some agent that can move between 6 different states using timeout transitions. These can represent different objects in rea-life, e.g. orders in a fast-moving consumer goods company where orders move between states like "Awaiting Approval", "Approved" or "Rejected". Or it can represent a Truck in a supply chain moving between "Transporting Order", " Break Down", "Refueling" etc.
We asked our intern to record the number of agents in each state and plot them on the stacked time chart. Having recently learned about lambda functions and the very useful utility functions from one of our previous blog posts, they decided to use the findAll() function and count the number of agents in each state and plot the total.
P.S. If you consider yourself and advanced AnyLogic user you should be able to spot the issue in the above model as well as the impact it will have on your model performance. Take a moment and make a prediction - leave a comment afterwards on the outcome of your prediction.
The model is really simple, and we plan to make major improvements to it e.g., adding a significant number of agents, more agent logic as well as some analysis features. But before we do, let's give it a quick run for one full year and check the results.
So here goes....
Everything looks good! The model completed the full year and the results look reasonable. The memory consumption is very small, less than 200 MB... but wait... have a look at the execution speed (Bottom right-hand corner)... over 1 minute to simulate a full year and we have not even begun to add significant detail to the model! How on earth are we going to run the model after we have doubled the number of agents and added tons of statistics, logic, and features....
Finding the performance issue
Luckily, optimizing java application performance has been around for a very long time. We don't need to rely on our intuition or trial and error to try and optimize our simulation model.
There are a number of commercial tools available for Java application profiling, the one I prefer is from eJ Technologies called Jprofiler and you can read up on it here. However, starting out you probably don't want to spend money on a tool you don't know how to use. Luckily, free applications like Visual VM are available that you can download for Mac and Windows here.
Installation on Mac
On Mac, if you run the DMG installer you will simply drag the application to your Applications folder.
Thereafter you can simply run the application from your Applications folder.
However, you will need to have Java installed on your machine run Visual VM
You can download a version of Java from various sources, the latest Java version from Oracle can be found here
Installation on Windows
What is Visual VM
VisualVM is a visual tool integrating several command‐line JDK tools and lightweight profiling capabilities. Designed for both production and development time use, it further enhances the capability of monitoring and performance analysis for the Java SE platform.
VisualVM enables you to monitor, profile, take thread dumps and browse heap dumps.
The main window of VisualVM opens when you launch the application. The Applications window is default displayed in the left pane of the main window. The Applications window enables you to quickly see the Java applications running on local and remote JVMs.
Using Visual VM to analyze model performance
Use the steps below to conduct an execution performance analysis on your application or watch the video here for step-by-step instructions
1) Run your model to the point where the execution is slow and pause it. In our case, this is anywhere after model startup since the model execution is the same throughout the model
2) Open the Visual VM application.
3) In the left pane, you will see 2 applications linked to your model. The AnyLogic database as well as the model itself.
4) Double click the application associated with your model, and select the monitor tab in the right pane
As you can see from the overview statistics we are using zero CPU, which makes sense as the model is paused. Now you can start running your simulation again.
You will see a significant increase in CPU usage depending on your machine configuration.
FYI: Monitoring an application using VisualVM Monitor view
CPU Usage: This chart plots the CPU usage over time and is used to indicate when model requirements are overloading the CPU
Heap: The Heap graph displays the total heap size and how much of the heap is currently used. Heap is similar to memory
Classes: The Classes graph displays an overview of the total number of loaded classes and shared classes.
Threads: The Threads graph displays an overview of the number of live and daemon threads in the application's JVM. You can use VisualVM to take a thread dump if you want to capture and view exact data on application threads at a specific point in time.
5) Navigate to the Sampler page, and select the CPU button in the Sample section. Wait a few seconds for the profiler to record statistics and then hit the stop button.
FYI: CPU Profiling
CPU profiler displays detailed data on method‐level CPU performance (execution time), showing the total execution time and number of invocations for each method. When analyzing application performance, VisualVM instruments all of the methods of the profiled application. Threads emit the "method entry" event when entering a method and generate the corresponding "method exit" event when exiting the method. Both of these events contain timestamps. This data is processed in real time.
6) Select the correct Thread
In the thread filer button deselect "Show all threads" and select only the "AnyLogic Execution Thread".
7) Analyze the thread
If we expand the different threads, we will see that just two functions take almost all CPU time "....Chart.updateData()" and "...TrasnsitionTimeOut.execute()"
If we expand these we see that the first has to do with the values we are plotting in the chart and the second has to do with the statechart transitions.
Since we can't do anything about the transitions in the model (without changing the core logic). Let's focus on the update chart function.
We see that in every update call, for each of the database objects that save the data for the specific agent state we want to show in the chart, the findAll() function is the eventual culprit.
Total Time
This refers to the total wall-clock time that your application or a specific method has taken. It includes all the time the application is running, including the time it spends waiting for resources like I/O operations, network communication, and any other delays that are not directly related to the CPU's processing time
Focus on this if your model's performance is affected by delays or waiting periods (e.g., database queries, file I/O, network requests). Optimizing these waits can lead to better overall performance
Total Time (CPU)
Improving performance
Now that we know exactly where the issue is, let's see if we can either:
a) Call this function less
b) Improve its internal performance
Since findAll() is an AnyLogic internal function, we can't try option b. So, let's just call the function once. Instead of cycling through the entire population of agents every time we want to find out who is in what state, let's cycle through it once and then increment a counter for each state. Once we cycle through all of them, we can add the counters to datasets, and the chart can simply use those databases.
Note: If you want to learn more about how the switch statement or any other Java coding is working check out our Java for AnyLogic course to learn more.
If we run the model now it completes in about 30 seconds, that is a 50% improvement!
Double-checking with the profiler shows that 83% of CPU time is due to the statechart transitions and a mere 8% due to the statistics recording event.
To test your profiling skills, why not download the little example model yourself and try repeating the exercise above. You can download the model from the cloud here - or from the link below.
Keep modeling
Jaco-Ben Vosloo
What next?
If you liked this post, you are welcome to read more posts by following the links above to similar posts. Remember to subscribe, so that you can get notifications via email or you can also join the mobile app here! Or follow us on any of the social media accounts for future updates. The links are in the Menu bar at the top or the footer at the bottom.
If you want to contact us for some advice, maybe a potential partnership or project or just to say "Hi!", feel free to get in touch here, and we will get back to you soon!
Comments