Skip to content

Commit 8148932

Browse files
author
kieranhejmadi01
committed
initial commit of content ready for PR and technical review
1 parent 3fe21b8 commit 8148932

13 files changed

Lines changed: 436 additions & 0 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
title: Accelerate Random Number Generation with OpenRNG and Performix
3+
description: Learn how to profile an example C++ data-processing workload on Arm Linux with Arm Performix, then accelerate random distribution generation using OpenRNG and Arm Performance Libraries.
4+
5+
minutes_to_complete: 45
6+
7+
who_is_this_for: This Learning Path is for C++ developers on Arm Linux who want to use profiling data to target optimization and speed up random number generation.
8+
9+
learning_objectives:
10+
- Build and run a baseline C++ data-processing workload on Arm Linux
11+
- Use Arm Performix Code Hotspots to identify the highest-impact optimization target
12+
- Build the workload with OpenRNG and Arm Performance Libraries
13+
- Validate speedups with a microbenchmark sweep across multiple data sizes
14+
15+
prerequisites:
16+
- An Arm Linux (aarch64) system, such as AWS Graviton
17+
- Basic C++ and CMake knowledge
18+
19+
author: Kieran Hejmadi
20+
21+
### Tags
22+
skilllevels: Introductory
23+
subjects: Performance and Architecture
24+
armips:
25+
- Neoverse
26+
tools_software_languages:
27+
- C++
28+
- CMake
29+
- Arm Performix
30+
- OpenRNG
31+
- Arm Performance Libraries
32+
operatingsystems:
33+
- Linux
34+
35+
further_reading:
36+
- resource:
37+
title: Install Arm Performix
38+
link: https://learn.arm.com/install-guides/performix/
39+
type: documentation
40+
- resource:
41+
title: Install Arm Performance Libraries
42+
link: https://learn.arm.com/install-guides/armpl/
43+
type: documentation
44+
- resource:
45+
title: OpenRNG project repository
46+
link: https://gitlab.arm.com/libraries/openrng
47+
type: documentation
48+
- resource:
49+
title: Find Code Hotspots with Arm Performix
50+
link: https://learn.arm.com/learning-paths/servers-and-cloud-computing/cpu_hotspot_performix/
51+
type: documentation
52+
53+
54+
55+
### FIXED, DO NOT MODIFY
56+
# ================================================================================
57+
weight: 1 # _index.md always has weight of 1 to order correctly
58+
layout: "learningpathall" # All files under learning paths have this same wrapper
59+
learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content.
60+
---
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
# ================================================================================
3+
# FIXED, DO NOT MODIFY THIS FILE
4+
# ================================================================================
5+
weight: 21 # The weight controls the order of the pages. _index.md always has weight 1.
6+
title: "Next Steps" # Always the same, html page title.
7+
layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing.
8+
---
240 KB
Loading
114 KB
Loading
75.2 KB
Loading
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
title: Get started
3+
weight: 2
4+
5+
### FIXED, DO NOT MODIFY
6+
layout: learningpathall
7+
---
8+
9+
## Prepare your environment
10+
11+
For this learning path, you will use an Arm Linux system (aarch64), such as an AWS Graviton instance running Amazon Linux 2023; it has been tested on an AWS Graviton 3 `c7g.metal` instance with 64 Neoverse V1 cores.
12+
13+
Install the following packages, replacing `dnf` with the package manager for your Linux distribution.
14+
15+
```bash
16+
sudo dnf update -y
17+
sudo dnf install -y git cmake g++ environment-modules python3 python3-pip
18+
```
19+
20+
Next, [install Arm Performix](https://learn.arm.com/install-guides/performix/) on the remote Arm Linux target (`c7g.metal` instance) and the graphical user interface on your local machine. There's no need to use the command-line interface (CLI).
21+
22+
Install the prebuilt Arm Performance Libraries on your Arm Linux system using the [install guide](https://learn.arm.com/install-guides/armpl/). Follow the instructions and load the module file. A module file configures your environment by setting paths and variables so the correct software and libraries are available. To verify the module loaded successfully, run:
23+
24+
```bash
25+
module list
26+
```
27+
28+
You should see the output below. You'll need to load the environment module with the `module load <arm-performance-lib>` command if it isn't already loaded.
29+
30+
```output
31+
Currently Loaded Modulefiles:
32+
1) arm-performance-libraries
33+
```
34+
35+
{{% notice Please Note %}}
36+
When you open a new terminal, you will need to reload the modulefile. To simplify this, you can add the modulefile path to your `~/.bashrc` so modules can be loaded directly with the `module load <arm-performance-lib>` command
37+
```bash
38+
echo "export MODULEPATH=$MODULEPATH:/opt/arm/modulefiles" >> ~/.bashrc
39+
```
40+
{{% /notice %}}
41+
42+
Clone and build the example project:
43+
44+
```bash
45+
git clone https://github.com/arm-education/Data-Processing-Example.git
46+
cd Data-Processing-Example
47+
```
48+
49+
In the next section, you examine what the data-processing example does and run the visualization helper.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
title: Run the baseline data-processing example
3+
weight: 3
4+
5+
### FIXED, DO NOT MODIFY
6+
layout: learningpathall
7+
---
8+
9+
## Understand the workflow and run the baseline
10+
11+
This Learning Path uses an example workload to demonstrate a data-processing pattern. The workflow processes synthetic 2D point data in three steps:
12+
13+
1. Generate two random distributions and add them.
14+
2. Count how many points lie inside a rectangular window.
15+
3. Compute the shortest distance from the origin.
16+
17+
Although this is a toy example, these three steps represent a common pattern in analytics pipelines: generate data, filter data, and reduce data to a metric. This pattern could be seen in workloads such as geospatial event filtering or scientific simulation.
18+
19+
In `src/main.cpp`, you can see this flow:
20+
21+
```cpp
22+
// STEP 1. Generate a distribution of 2D Points that is the sum of a Gaussian and Uniform distribution
23+
24+
const Vec1D distribution = generateDistribution(NUM_POINTS, BASIC_RNG::GAUSSIAN, BASIC_RNG::UNIFORM, meanAndStdDeviationParams, minAndMaxParams);
25+
26+
// STEP 2. Calculate the number of points that fit within a 2D window
27+
28+
Rectangle window(10.0,10.0,50.0,50.0);
29+
int numberOfPoints = window.countPointsInRectangle(distribution);
30+
std::cout << "Number of Data Points = " << NUM_POINTS
31+
<< " | Number of Points within Window ( ["
32+
<< window.bottomLeft[0] << ", " << window.bottomLeft[1] << "] , ["
33+
<< window.topRight[0] << ", " << window.topRight[1]
34+
<< "] ) = " << numberOfPoints << std::endl;
35+
36+
37+
// STEP 3. Calculate the magnitude of the smallest point within the distribution
38+
39+
float shortestDistance = min_length(distribution.getData());
40+
std::cout << "Shortest Distance from Origin = " << shortestDistance << std::endl;
41+
```
42+
43+
Manually timing specific sections of code can help identify bottlenecks, but it requires adding instrumentation and risks overlooking other hotspots that were not explicitly measured. Instead, use Arm Performix Code Hotspots in to measure the entire program and observe where CPU cycles are actually spent.
44+
45+
Build and run the baseline executable:
46+
47+
```bash
48+
cmake -S . -B build
49+
cmake --build build --target main
50+
./build/src/main
51+
```
52+
53+
You should see the following output. The example generates 16,384 points on an x and y axis. The distribution is the sum of a Gaussian distribution with a mean and standard deviation of 30 and 50 respectively, and a uniform distribution with a min and max of 10 and 100. For each point, the workload checks whether it lies within a window with bottom-left and top-right coordinates of [10,10] and [50,50] respectively, then finds the point closest to the origin.
54+
55+
```output
56+
Number of Data Points = 16384 | Number of Points within Window ( [10, 10] , [50, 50] ) = 586
57+
Shortest Distance from Origin = 1.88536
58+
```
59+
60+
To confirm the data distribution is being generated correctly, we can export our data to be processed by a basic python script.
61+
62+
```bash
63+
cmake -S . -B build -DBUILD_TESTS=1
64+
cmake --build build --target generate_visualization_baseline
65+
./build/tests/generate_visualization_baseline
66+
```
67+
68+
The command writes `vector_data.csv`. Next, create a Python environment and render the plot:
69+
70+
```bash
71+
python3 -m venv venv
72+
source venv/bin/activate
73+
pip3 install -r scripts/requirements.txt
74+
python3 scripts/visualize_vectors.py
75+
```
76+
77+
You should get an output image similar to this:
78+
79+
![Scatter plot generated from vector_data.csv showing the point distribution#center](./vector_data.png)
80+
81+
## What you've learned and what's next
82+
83+
You validated the baseline workflow and generated output you can inspect. Next, you use Arm Performix Code Hotspots to identify the true optimization target from measured runtime data.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
title: Identify code hotspots with Arm Performix
3+
weight: 4
4+
5+
### FIXED, DO NOT MODIFY
6+
layout: learningpathall
7+
---
8+
9+
## Profile the baseline with Code Hotspots
10+
11+
Arm Performix includes a **Code Hotspots** recipe that shows which functions consume the most CPU time. This helps you prioritize optimization work based on evidence instead of guess work. If you haven't come across flame graphs or sample-based profiling, see this introductory [Learning Path](https://learn.arm.com/learning-paths/servers-and-cloud-computing/cpu_hotspot_performix/).
12+
13+
From your local machine, open the Arm Performix GUI and run Code Hotspots recipe on the baseline executable at `./build/src/main`.
14+
15+
![Arm Performix GUI showing where to create a new Code Hotspots analysis run for the baseline executable#center](./run-code-hotspot.jpg)
16+
17+
18+
![Arm Performix GUI showing flame graph#center](./flame-graph.jpg)
19+
20+
21+
The flame graph shows CPU time distribution across the call stack, where wider blocks indicate higher cumulative execution time. The dominant feature is the wide base associated with `generateDistribution(...)`, indicating it is the primary hotspot and consumes the largest share of execution time.
22+
23+
Drilling into that region, most of the work comes from standard library random generation routines, particularly paths involving `std::normal_distribution<float>`. Those stacks are visibly wider than those involving `std::uniform_real_distribution<float>`, indicating that Gaussian (normal) sampling is significantly more expensive in terms of CPU cycles than uniform sampling. The imbalance might not have been obvious from higher-level instrumentation, because both operations conceptually generate random numbers, but their computational cost differs.
24+
25+
In contrast, functions related to computing properties such as counting points within a rectangle or identifying a minimum point (for example, `min_length`-type operations) occupy relatively narrow regions of the graph, contributing only a small fraction of total runtime and representing no meaningful bottleneck.
26+
27+
28+
## What you've learned and what's next
29+
30+
You used profiling data to highlight which function to optimize. Next, you accelerate random generation by enabling OpenRNG through Arm Performance Libraries.
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
title: Accelerate distribution generation with OpenRNG
3+
weight: 5
4+
5+
### FIXED, DO NOT MODIFY
6+
layout: learningpathall
7+
---
8+
9+
## Accelerate Hotspot with OpenRNG and Arm Performance Libraries
10+
11+
In this step, you replace the baseline random-number path with OpenRNG from Arm Performance Libraries.
12+
13+
Arm Performance Libraries includes numerical routines tuned for Arm processors. Those Arm-tuned routines can speed up execution by reducing overhead and improving throughput for compute-heavy kernels.
14+
15+
OpenRNG is the random-number component in Arm Performance Libraries. It exposes a Vector Statistical Library (VSL) API, where *VSL* means generating distributions in vector form (many values per call) instead of one sample at a time.
16+
17+
This Learning Path uses that vector API to generate Gaussian values in bulk. Bulk generation through a stream object lowers per-sample overhead, which is why this section can reduce runtime in the hotspot.
18+
19+
OpenRNG is API-compatible with the Intel oneMKL VSL RNG interface, so distribution call sites remain familiar when you move between x86 and AArch64 builds. You mainly switch build configuration, not workload logic. See the [OpenRNG developer reference guide](https://developer.arm.com/documentation/101004/2601/Open-Random-Number-Generation-Reference-Guide/RNG-Introduction/An-overview-of-OpenRNG?lang=en) for details.
20+
21+
In `src/vec1d.cpp`, the `USE_ARMPL` macro controls which implementation is compiled. In this project, enabling `USE_ARMPL` selects the AArch64 OpenRNG path:
22+
23+
```cpp
24+
#if USE_ARMPL
25+
#if defined(__aarch64__)
26+
#include <amath.h>
27+
#include <openrng.h>
28+
#else
29+
#error "USE_ARMPL enabled but not on AArch64"
30+
#endif
31+
#endif
32+
```
33+
34+
The OpenRNG path creates one VSL stream and fills a contiguous buffer for both coordinates. `count` is `2 * v.getSize()` because each point has `x` and `y` values:
35+
36+
```cpp
37+
VSLStreamStatePtr stream;
38+
vslNewStream(&stream, VSL_BRNG_SFMT19937, 777);
39+
40+
const int count = 2 * v.getSize();
41+
std::vector<float> tmp(count);
42+
```
43+
44+
For Gaussian generation, the code calls the single-precision VSL routine `vsRngGaussian`. Here, `param_a` is the mean and `param_b` is the standard deviation:
45+
46+
```cpp
47+
vsRngGaussian(
48+
VSL_RNG_METHOD_UNIFORM_STD,
49+
stream,
50+
count,
51+
tmp.data(),
52+
param_a, // mean
53+
param_b // standard deviation
54+
);
55+
```
56+
57+
After bulk generation, values are mapped back into your `Point` vector:
58+
59+
```cpp
60+
auto& data = v.getData();
61+
for (int i = 0; i < v.getSize(); i++){
62+
data[i]._x = tmp[2*i];
63+
data[i]._y = tmp[2*i+1];
64+
}
65+
66+
vslDeleteStream(&stream);
67+
```
68+
69+
70+
71+
Now build and run the accelerated variant:
72+
73+
```bash
74+
make clean
75+
cmake -S . -B build -DUSE_ARMPL=1
76+
cmake --build build --target main_with_apl
77+
./build/src/main_with_apl
78+
```
79+
80+
## Analyze the flame graph
81+
82+
Now profile the accelerated binary in the Arm Performix GUI using `./build/src/main_with_apl` as the workload.
83+
84+
![Arm Performix GUI showing a Code Hotspots run configured for the accelerated executable main_with_apl#center](./hotspot-with-apl.jpg)
85+
86+
{{% notice Please Note %}}
87+
If you encounter an error when trying to run the workload through Arm Performix, it's because the binary runs from a fresh environment without the Arm Performance Library environment module loaded. The easiest workaround is to add the `LD_LIBRARY_PATH` environment variable to your `.bashrc` file.
88+
89+
```bash
90+
91+
echo "export LD_LIBRARY_PATH=/opt/arm/arm-performance-libraries/lib:${LD_LIBRARY_PATH}" >> ~/.bashrc
92+
```
93+
Alternatively, if using the CLI, you can pass in the environment variable with the `--env` argument. As of Performix 2026.2.0, you are unable to pass through environment variables via the GUI.
94+
95+
{{% /notice %}}
96+
97+
The flame graph shows `main`, but Arm Performance Libraries functions (including OpenRNG) do not appear above it. Instead, most samples are attributed to dynamic loader functions (for example, `_dl_*`), which indicates missing symbol resolution. This often happens on Linux when pre-built shared libraries (`*.so`) do not include debug symbols. The profiler cannot resolve internal library calls, so stacks appear truncated.
98+
99+
To fix this, rebuild openRNG from source with debug information enabled (e.g., -g), so the library functions show up correctly above main.
100+
101+
![Arm Performix flame graph with split attribution for shared-library calls from the OpenRNG path#center](./flame-graph-detached.jpg)
102+
103+
Run the following command from the root of the project directory to build `openrng` with debug.
104+
105+
```bash
106+
git clone https://gitlab.arm.com/libraries/openrng.git && cd openrng
107+
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$PWD/install
108+
cmake --build build -j $(nproc -1)
109+
cmake --install build
110+
```
111+
112+
Run the command below to compile from the root of the project directory.
113+
114+
115+
```bash
116+
g++ --std=c++20 -g -O0 \
117+
src/main.cpp src/vec1d.cpp src/point.cpp src/rectangle.cpp src/export_data.cpp \
118+
-DUSE_ARMPL=1 \
119+
-I./include \
120+
-I./openrng/install/include \
121+
-I${ARMPL_DIR}/include \
122+
-L./openrng/install/lib64 \
123+
-L${ARMPL_DIR}/lib \
124+
-lopenrng -lamath -lm \
125+
-Wl,-rpath,$PWD/openrng/install/lib64 \
126+
-Wl,-rpath,${ARMPL_DIR}/lib \
127+
-o ./build/debug_openrng
128+
```
129+
130+
{{% notice Please Note %}}
131+
132+
OpenRNG uses the CMake build system, so as an alternative you can update your CMake configuration to fetch from the third-party library. The direct command above is used here primarily for convenience.
133+
134+
{{% /notice %}}
135+
136+
From Performix, run the code hotspot recipe and target the `./build/debug_openrng` binary.
137+
138+
![Arm Performix flame graph with OpenRNG symbols correctly resolved and stacked above the calling functions#center](./openrng_flame_graph_with_debug.jpg)
139+
140+
You can now correctly see the OpenRNG frames appearing above your calling functions. Because hotspots are identified through periodic sampling, the measurements provide limited direct insight into exact wall-clock time. Next, you will measure the speedup of the hot function and examine how it varies across different data sizes.

0 commit comments

Comments
 (0)