|
| 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 | + |
| 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 | + |
| 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 | + |
| 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