Skip to content

Commit 9acab46

Browse files
Merge pull request #3004 from jasonrandrews/review3
first tech review of CPU microarchitecture
2 parents f8eaf8c + 1d62153 commit 9acab46

32 files changed

Lines changed: 131 additions & 65 deletions
Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,71 @@
11
---
2-
title: Setup
2+
title: Set up the target environment and compile the application
33
weight: 2
44

55
### FIXED, DO NOT MODIFY
66
layout: learningpathall
77
---
88

9-
This Learning Path uses Arm Performix CPU Mircoarchitecture and Instruction Mix recipes to analyze performance in a sample application.
9+
To analyze performance bottlenecks, you need an environment and a sample application to profile. In this section, you configure an Arm Performix connection and build a Mandelbrot set generator.
10+
11+
A Mandelbrot set generator is a classic computer science application used to test computational performance. It calculates a famous mathematical fractal by performing intense, repeated mathematical operations (often floating-point) for every pixel in a large image. Because the math for each pixel is independent of the others, it is a highly parallelizable workload that is perfect for demonstrating CPU optimizations like vectorization and loop unrolling.
1012

1113
## Before you begin
1214

13-
Use the Performix [installation guide](/install-guides/atp/) to install the tool if this is your first run. From the host machine, open the **Targets** tab, set up an SSH connection to the target that runs the workload, and test the connection. In this Learning Path's examples, I'll connect to an Arm Neoverse V1 workstation.
15+
Make sure Arm Performix installed on your host machine. The host machine is your local computer where the Arm Performix GUI runs, and it can be a Windows, macOS, or Linux machine. The target machine is the Linux server where your application is compiled and where the application runs.
16+
17+
If you do not have Arm Performix installed, see the [Arm Performix install guide](/install-guides/atp/).
18+
19+
From the host machine, open the Arm Performix application and navigate to the **Targets** tab. Set up an SSH connection to the target that runs the workload, and test the connection. For the examples in this guide, you connect to an Arm Neoverse-based server.
20+
21+
The Arm Performix collection agent requires Python and `binutils` to run on the target machine.
22+
23+
Connect to your target machine using SSH and install these required OS packages.
24+
25+
For Ubuntu and other Debian-based distributions, run the following command:
1426

15-
Install required OS packages on the target. For Debian-based distributions, run:
1627
```bash
1728
sudo apt-get install python3 python3-venv binutils
1829
```
1930

20-
## Build sample application on remote server
31+
## Build the sample application on the target machine
2132

22-
Connect to your target machine and download the sample application for this Learning Path, a Mandelbrot set generator.
23-
The code is available under the [Arm Education License](https://github.com/arm-university/Mandelbrot-Example?tab=License-1-ov-file). Create a directory where you want to store and build the example, then run:
33+
Download the sample application, which is a Mandelbrot set generator provided under the [Arm Education License](https://github.com/arm-university/Mandelbrot-Example?tab=License-1-ov-file). Create a directory to store and build the example, then run the following commands:
2434

2535
```bash
36+
cd $HOME
2637
git clone https://github.com/arm-university/Mandelbrot-Example.git
2738
cd Mandelbrot-Example && mkdir images builds
2839
```
2940

30-
Install a C++ compiler by using your operating system's package manager.
41+
Install a C++ compiler using your operating system's package manager. For Ubuntu and other Debian-based distributions, run the following command:
3142

3243
```bash
3344
sudo apt install build-essential
3445
```
3546

36-
Build the application:
47+
Run the provided setup script to build the application:
3748

3849
```bash
3950
./build.sh
4051
```
4152

42-
The binary in the `./builds/` directory generates an image similar to the fractal below.
53+
When the build completes, a binary named `mandelbrot-parallel` is created in the `./builds` directory.
54+
55+
The application requires one argument: the number of threads to use. Run this new executable with 4 threads:
56+
57+
```bash
58+
./builds/mandelbrot-parallel 4
59+
```
60+
61+
The application generates a bitmap image file in your `./images` directory that looks similar to the following fractal:
62+
63+
![Mandelbrot set fractal generated by the sample application#center](./green-parallel-512.webp "Mandelbrot Set")
64+
65+
## What you've accomplished and what's next
66+
67+
In this section:
68+
- You set up the target machine and established an SSH connection.
69+
- You built the Mandelbrot sample application.
4370

44-
![Green-Parallel-512.bmp](./Green-Parallel-512.bmp)
71+
Next, you will use the CPU Microarchitecture recipe to identify performance bottlenecks in the application.
Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,76 @@
11
---
2-
title: Find Bottlenecks with CPU Microarchitecture
2+
title: Identify application bottlenecks with the CPU Microarchitecture recipe
33
weight: 3
44

55
### FIXED, DO NOT MODIFY
66
layout: learningpathall
77
---
88

9-
## Run CPU Microarchitecture analysis
9+
## Run the CPU Microarchitecture recipe
1010

11-
As shown in the `main.cpp` listing below, the program generates a 1920×1080 bitmap image of the fractal. To identify performance bottlenecks, run the CPU Microarchitecture recipe in Arm Performix (APX). APX uses microarchitectural sampling to show which instruction pipeline stages dominate program latency, then highlights ways to improve those bottlenecks.
11+
To identify performance bottlenecks, run the CPU Microarchitecture recipe in Arm Performix. Arm Performix uses microarchitectural sampling to show which instruction pipeline stages dominate program latency, and then highlights ways to improve those bottlenecks.
1212

13-
14-
{{% notice Specify the example output file %}}
15-
Replace the first string argument in `myplot.draw()` with the absolute path to your image folder, then rebuild the application. Otherwise, the image is written to `/tmp/atperf/tools/atperf-agent`, which is periodically deleted.
16-
{{% /notice %}}
13+
Start by reviewing the code in `main.cpp`, the program generates a 1920×1080 bitmap image of the fractal.
1714

1815
```cpp
1916
#include "Mandelbrot.h"
2017
#include <iostream>
2118

2219
using namespace std;
2320

24-
int main(){
21+
int main(int argc, char* argv[]){
22+
23+
const int NUM_THREADS = std::stoi(argv[1]);
24+
std::cout << "Number of Threads = " << NUM_THREADS << std::endl;
2525

26-
Mandelbrot::Mandelbrot myplot(1920, 1080);
27-
myplot.draw("/path/to/images/green.bmp", Mandelbrot::Mandelbrot::GREEN);
26+
Mandelbrot::Mandelbrot myplot(1920, 1080, NUM_THREADS);
27+
myplot.draw("/home/ec2-user/Mandelbrot-final/Mandelbrot-Example/images/Green-Parallel-512.bmp", Mandelbrot::Mandelbrot::GREEN);
2828

2929
return 0;
3030
}
3131
```
3232
33-
On your host machine, open Arm Performix and select the **CPU Microarchitecture** recipe.
33+
When Arm Performix launches the executable on the target machine, it does so from a temporary agent directory, `/tmp/atperf/tools/atperf-agent`. If your code uses a relative path to save the image, the image is written to that temporary folder and might be deleted.
34+
35+
To prevent this, edit the `myplot.draw()` line in `main.cpp` to use the absolute path to your project's image folder (for example, `/home/ubuntu/Mandelbrot-Example/images/Green-Parallel-512.bmp`), and then rebuild the application.
36+
37+
In the Arm Performix application on your host machine, select the **CPU Microarchitecture** recipe.
3438
35-
![config](./cpu-uarch-config.jpg)
39+
![Arm Performix CPU Microarchitecture configuration screen#center](./cpu-uarch-config.webp "CPU Microarchitecture Configuration")
3640
37-
Select the target you configured in the setup phase. If this is your first run on this target, you likely need to select **Install Tools** to copy collection tools to the target. Next, select the **Workload type**. You can sample the whole system or attach to an existing process, but in this exercise you launch a new process.
41+
Select the target you configured in the setup section. If this is your first run on this target, you might need to select **Install Tools** to copy the collection tools to the target. After the tools are installed, you see the target is now ready.
3842
39-
{{% notice Common Gotcha%}}
43+
Next, select the **Workload type** and select **Launch a new process**.
44+
45+
Enter the absolute path to your executable in the **Workload** field. For example, `/home/ubuntu/Mandelbrot-Example/builds/mandelbrot-parallel`. Make sure to add the number of threads argument.
46+
47+
{{% notice Note %}}
4048
Use the full path to your executable because the **Workload** field does not currently support shell-style path expansion.
4149
{{% /notice %}}
4250
43-
You can set a time limit for the workload and customize metrics if you already know what to investigate.
51+
Before starting the analysis, you can customize the configuration. For instance, you can set a time limit for the workload or choose specific metrics to investigate. You can also adjust the sampling rate (High, Normal, or Low) to balance collection overhead against sampling granularity. Because this Mandelbrot example is a native C++ application, you can ignore the **Collect managed code stacks** toggle, which is used for Java or .NET workloads.
4452
45-
The **Collect managed code stacks** toggle matters for Java/JVM or .NET workloads.
53+
When your configuration is ready, select **Run Recipe** to launch the workload and collect the performance data.
4654
47-
You can also select High, Normal, or Low sampling rates to trade off collection overhead and sampling granularity.
55+
## View the run results
4856
49-
Select **Run Recipe** to launch the workload and collect performance data.
57+
Arm Performix generates a high-level instruction pipeline view, highlighting where the most time is spent.
5058
51-
## View Run Results
59+
![Arm Performix high-level instruction pipeline results#center](cpu-uarch-results.webp "Instruction Pipeline View")
5260
53-
Performix generates a high-level instruction pipeline view, highlighting where most time is spent.
61+
In this breakdown, Backend Stalls dominate the samples. Within that category, work is split between Load Operations and integer and floating-point operations.
62+
There is no measured SIMD activity, even though this workload is highly parallelizable.
5463
55-
![cpu-uarch-results.jpg](cpu-uarch-results.jpg)
64+
The **Insights** panel highlights ALU contention as a likely improvement opportunity:
5665
57-
In this breakdown, Backend Stalls dominate samples. Within that category, work is split between Load Operations and integer and floating-point operations.
58-
There is no measured SIMD activity, even though this workload is highly parallelizable.
66+
![Arm Performix insights panel highlighting ALU contention#center](cpu-uarch-insights.webp "Insights Panel")
67+
68+
To inspect executed instruction types in more detail, use the Instruction Mix recipe in the next step.
5969
60-
The **Insights** panel highlights ALU contention as a likely improvement opportunity.
70+
## What you've accomplished and what's next
6171
62-
![cpu-uarch-insights.jpg](cpu-uarch-insights.jpg)
72+
In this section:
73+
- You ran the CPU Microarchitecture recipe on the Mandelbrot application.
74+
- You identified that the application spends most of its time in Backend Stalls without using SIMD operations.
6375
64-
To inspect executed instruction types in more detail, use the Instruction Mix recipe in the next step.
76+
Next, you will run the Instruction Mix recipe to confirm where optimization opportunities exist and implement vectorization.

content/learning-paths/servers-and-cloud-computing/performix-microarchitecture/3-instruction-mix.md

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,54 @@
11
---
2-
title: Understand Instruction Mix
2+
title: Analyze SIMD utilization with the Instruction Mix recipe
33
weight: 4
44

55
### FIXED, DO NOT MODIFY
66
layout: learningpathall
77
---
88

9-
## Run Instruction Mix
9+
## Run the Instruction Mix recipe
1010

11-
The previous CPU Microarchitecture analysis showed that the sample application used no single instruction, multiple data (SIMD) operations, which points to an optimization opportunity. Run the Instruction Mix recipe to learn more. The Instruction Mix launch panel is similar to CPU Microarchitecture, but it does not include options to choose metrics. Again, enter the full path to the workload. This Mandelbrot example is native C++ code, not Java or .NET, so you do not need to collect managed code stacks.
11+
The previous CPU Microarchitecture analysis showed that the sample application used no single instruction, multiple data (SIMD) operations, which points to an optimization opportunity. Run the Instruction Mix recipe to learn more. The Instruction Mix launch panel is similar to CPU Microarchitecture, but it does not include options to choose metrics. Again, enter the full path to the workload.
1212

13-
![instruction-mix-config.jpg](instruction-mix-config.jpg)
13+
Select **Dynamic** f for the **Analysis Mode**.
1414

15+
![Arm Performix Instruction Mix configuration screen#center](instruction-mix-config.webp "Instruction Mix Configuration")
1516

1617
The results below confirm a high number of integer and floating-point operations, with no SIMD operations. The **Insights** panel suggests vectorization as a path forward, lists possible root causes, and links to related Learning Paths.
1718

18-
![instruction-mix-results.jpg](instruction-mix-results.jpg)
19+
![Arm Performix Instruction Mix results showing high integer and floating point operations#center](instruction-mix-results.webp "Instruction Mix Results")
1920

20-
## Vectorize
21+
## Vectorize the application
2122

22-
The CPU Hotspots recipe in [Find CPU cycle hotspots with Arm Performix](/learning-paths/servers-and-cloud-computing/cpu_hotspot_performix/) helps you identify which functions consume the most time. In this example, `Mandelbrot::draw` and its inner function `Mandelbrot::getIterations` dominate runtime. A vectorized version is available in the [instruction-mix branch](https://github.com/arm-education/Mandelbrot-Example/tree/instruction-mix). This branch uses Neon operations for Neoverse N1, while your platform might support alternatives such as SVE or SVE2.
23+
To address the lack of SIMD operations, you can vectorize the application's most intensive functions. For the Mandelbrot application, `Mandelbrot::draw` and its inner `Mandelbrot::getIterations` function consume most of the runtime. A vectorized version is available in the [instruction-mix branch](https://github.com/arm-education/Mandelbrot-Example/tree/instruction-mix). This branch uses Neon operations, which run on any Neoverse system. Your system might support alternatives such as SVE or SVE2 which can also be used.
2324

24-
After you rebuild the application and run Instruction Mix again, integer and floating-point operations are greatly reduced and replaced by a smaller set of SIMD instructions.
25+
Connect to your target machine using SSH and navigate to your project directory. Because you modified `main.cpp` earlier, you must stash your changes before switching to the `instruction-mix` branch. Then, rebuild the application:
2526

26-
![instruction-mix-simd-results.jpg](instruction-mix-simd-results.jpg)
27+
```bash
28+
cd $HOME/Mandelbrot-Example
29+
git stash
30+
git checkout instruction-mix
31+
./build.sh
32+
```
33+
34+
After you rebuild the application and run the Instruction Mix recipe again, integer and floating-point operations are greatly reduced and replaced by a smaller set of SIMD instructions.
2735

28-
## Assess improvements
36+
![Arm Performix Instruction Mix results after vectorization showing increased SIMD operations#center](instruction-mix-simd-results.webp "SIMD Instruction Mix Results")
37+
38+
## Assess the performance improvements
2939

3040
Because you are running multiple experiments, give each run a meaningful nickname to keep results organized.
31-
![rename-run.jpg](rename-run.jpg)
41+
![Arm Performix run renaming interface#center](rename-run.webp "Rename Run")
3242

3343
Use the **Compare** feature at the top right of an entry in the **Runs** view to select another run of the same recipe for comparison.
3444

35-
![compare-with-box.jpg](compare-with-box.jpg)
45+
![Arm Performix compare view selection box#center](compare-with-box.webp "Compare Runs")
3646

3747
This selection box lets you choose any run of the same recipe type. The ⇄ arrows swap which run is treated as the baseline and which is current.
3848

39-
After you select two runs, Performix overlays them so you can review category changes in one view. In the new run, note that
49+
After you select two runs, Arm Performix overlays them so you can review category changes in one view. In the new run, note that
4050

41-
![instruction-mix-diff-results.jpg](instruction-mix-diff-results.jpg)
51+
![Arm Performix comparison showing differences in instruction mix#center](instruction-mix-diff-results.webp "Instruction Mix Comparison")
4252
Compared to the baseline, floating-point operations, branch operations, and some integer operations have been traded for loads, stores, and SIMD operations.
4353
Execution time also improves significantly, making this run nearly four times faster.
4454

@@ -60,10 +70,10 @@ user 0m8.331s
6070
sys 0m0.016s
6171
```
6272

63-
## CPU Microarchitecture results comparison
73+
## Compare the CPU Microarchitecture results
6474

6575
The CPU Microarchitecture recipe also supports a **Compare** view that shows percentage-point changes in each stage and instruction type.
66-
![cpu-uarch-simd-results-diff.jpg](cpu-uarch-simd-results-diff.jpg)
76+
![Arm Performix CPU Microarchitecture comparison showing changes in each stage#center](cpu-uarch-simd-results-diff.webp "CPU Microarchitecture Difference View")
6777

6878
You can now see that Load and Store operations account for about 70% of execution time. **Insights** offers several explanations because multiple issues can contribute to the root cause.
6979
```
@@ -78,7 +88,9 @@ POSSIBLE CAUSES
7888
- Instruction dependencies that create pipeline bubbles
7989
```
8090

81-
Next, add optimization flags to the compiler to enable more aggressive loop unrolling.
91+
## Apply compiler optimizations for loop unrolling
92+
93+
To address the new load and store bottlenecks, add optimization flags to the compiler to enable more aggressive loop unrolling. Edit the `build.sh` script to include these flags in the `CXXFLAGS` array:
8294
```bash
8395
# build.sh
8496
CXXFLAGS=(
@@ -92,7 +104,9 @@ Next, add optimization flags to the compiler to enable more aggressive loop unro
92104
)
93105
```
94106

95-
Runtime improves again, with an additional 11x speedup over the SIMD build that uses default compiler flags.
107+
After saving the file, run `./build.sh` to compile the application with the new flags.
108+
109+
Runtime improves again, with an additional 11x speedup over the SIMD build that used the default compiler flags.
96110

97111

98112
```bash { command_line="root@localhost | 2-6" }
@@ -105,8 +119,17 @@ sys 0m0.014s
105119
```
106120

107121
Another CPU Microarchitecture measurement shows that Load and Store bottlenecks are almost eliminated. SIMD floating-point operations now dominate execution, which indicates the application is better tuned to feed floating-point execution units.
108-
![high-simd-utilization.jpg](high-simd-utilization.jpg)
122+
![Arm Performix insights showing high SIMD utilization#center](high-simd-utilization.webp "High SIMD Utilization")
109123

110124
The program still generates the same output, and runtime drops from 31 s to less than 1 s, a 43x speedup.
111125

112-
![performance-improvement.jpg](performance-improvement.jpg)
126+
![Arm Performix results highlighting total performance improvement#center](performance-improvement.webp "Performance Improvement Summary")
127+
128+
## What you've accomplished and what's next
129+
130+
In this section:
131+
- You used the Instruction Mix recipe to confirm a lack of SIMD operations.
132+
- You vectorized the sample application and verified the shift toward SIMD execution.
133+
- You applied compiler loop unrolling to relieve backend load/store bottlenecks, achieving over 40x speedup.
134+
135+
You are now ready to analyze and optimize your own native C/C++ applications on Arm Neoverse using Arm Performix. Review the next steps to continue your learning journey.

0 commit comments

Comments
 (0)