CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
776 lines
24 KiB
ReStructuredText
776 lines
24 KiB
ReStructuredText
.. meta::
|
|
:description: This chapter describes a set of best practices designed to help
|
|
developers optimize the performance of HIP-capable GPU architectures.
|
|
:keywords: AMD, ROCm, HIP, CUDA, performance, guidelines, optimization, how-to
|
|
|
|
.. _how_to_performance_guidelines:
|
|
|
|
*******************************************************************************
|
|
Performance guidelines
|
|
*******************************************************************************
|
|
|
|
The AMD HIP performance guidelines provide practical, actionable techniques for
|
|
optimizing application performance on AMD GPUs. This guide focuses on
|
|
step-by-step instructions and best practices for improving performance.
|
|
|
|
For theoretical foundations and performance concepts, see
|
|
:doc:`../understand/performance_optimization`.
|
|
|
|
Optimization workflow
|
|
=====================
|
|
|
|
Follow this systematic approach to optimize GPU performance:
|
|
|
|
1. **Profile and measure baseline**
|
|
|
|
Use ``rocprofv3`` to identify bottlenecks:
|
|
|
|
.. code-block:: bash
|
|
|
|
rocprofv3 --stats --<tracing_option> -- <application_path>
|
|
|
|
Collect metrics on kernel execution time, memory bandwidth, occupancy, and
|
|
CU utilization. For more details on using ``rocprofv3`` for application
|
|
tracing and profiling, see :doc:`rocprofv3 documentation
|
|
<rocprofiler-sdk:how-to/using-rocprofv3>`.
|
|
|
|
2. **Analyze metrics to identify bottlenecks**
|
|
|
|
Determine if kernels are compute-bound or memory-bound. Check arithmetic
|
|
intensity, memory bandwidth achieved vs peak, and compute throughput.
|
|
|
|
For understanding the roofline model, see :ref:`roofline_model`.
|
|
|
|
3. **Apply targeted optimizations**
|
|
|
|
Based on identified bottlenecks, apply techniques from this guide.
|
|
|
|
4. **Verify improvements**
|
|
|
|
Re-profile to confirm performance gains.
|
|
|
|
5. **Iterate**
|
|
|
|
Repeat until performance goals are met.
|
|
|
|
Profiling and analysis tools
|
|
=============================
|
|
|
|
ROCm provides a comprehensive suite of profiling and analysis tools that help developers understand and optimize GPU performance. These tools are essential for identifying bottlenecks and evaluating the effectiveness of performance optimizations.
|
|
|
|
rocprofv3
|
|
---------
|
|
|
|
The tool :doc:`rocprofv3 <rocprofiler-sdk:how-to/using-rocprofv3>` provides
|
|
command-line-driven profiling for detailed performance analysis. It collects
|
|
metrics on kernel execution time, memory bandwidth, warp occupancy, VALU
|
|
utilization, and instruction-level counters.
|
|
|
|
``rocprofv3`` integrates with the
|
|
:doc:`rocProfiler-SDK framework <rocprofiler-sdk:index>` to collect hardware
|
|
traces and API-level timing data. The collected data can be exported in JSON and
|
|
CSV formats for further analysis or visualization.
|
|
|
|
Key capabilities:
|
|
|
|
* Kernel execution profiling
|
|
* Memory bandwidth analysis
|
|
* Warp occupancy metrics
|
|
* Compute unit utilization
|
|
* Instruction-level performance counters
|
|
* API call tracing
|
|
* Hardware event collection
|
|
|
|
Trace visualization with Perfetto
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
For application-level tracing, ``rocprofv3`` can generate traces compatible with
|
|
`Perfetto <https://ui.perfetto.dev>`__, a third-party, open-source trace viewer.
|
|
This enables visualization of the complete timeline of application execution,
|
|
including the temporal relationships among host operations, kernel launches,
|
|
memory transfers, and synchronization events.
|
|
|
|
Using Perfetto with traces generated by ``rocprofv3`` can help identify
|
|
performance issues caused by API overhead, inefficient synchronization, or
|
|
insufficient overlap between computation and data movement.
|
|
|
|
The following trace data is available for visualization:
|
|
|
|
* Application execution timelines
|
|
* HIP API call tracking and timing
|
|
* ROCm library call analysis
|
|
* Host-device synchronization events
|
|
* Memory transfer operations
|
|
|
|
ROCprof Compute Viewer
|
|
----------------------
|
|
|
|
`ROCprof Compute Viewer <https://rocm.docs.amd.com/projects/rocprof-compute-viewer/en/latest/>`__
|
|
provides a GUI-based environment for analyzing GPU kernel performance data. It
|
|
delivers detailed kernel-level insights, including counter correlation and
|
|
hierarchical performance breakdowns, helping developers interpret execution
|
|
patterns and identify optimization opportunities.
|
|
|
|
Key features:
|
|
|
|
* Kernel performance counter analysis
|
|
* Counter correlation and visualization
|
|
* Hierarchical kernel performance breakdown
|
|
* Interactive performance data exploration
|
|
* Kernel-level optimization insights
|
|
|
|
|
|
AMD System Management Interface
|
|
--------------------------------
|
|
|
|
The :doc:`AMD System Management Interface <amdsmi:index>` (AMD SMI) is a
|
|
command-line utility for querying, monitoring, and managing AMD GPUs. It
|
|
provides system administrators and developers with detailed, real-time
|
|
information about GPU hardware, utilization, and power metrics.
|
|
|
|
``amd-smi`` reports the following categories of information:
|
|
|
|
* GPU identity information, such as the card name, device ID, and PCI bus
|
|
location
|
|
* Live utilization metrics, including GPU activity, memory usage, clock speeds,
|
|
and active processes
|
|
* Power and thermal readings, such as temperature, fan speed, voltage, and
|
|
power draw
|
|
* Performance states and limits, such as available frequency levels, clock
|
|
throttling, and voltage controls
|
|
|
|
These metrics are retrieved through the AMD SMI C API, which exposes a stable,
|
|
scriptable interface for system and performance monitoring tools.
|
|
|
|
``amd-smi`` also supports management operations, including:
|
|
|
|
* Setting power caps and performance profiles
|
|
* Adjusting clock frequencies
|
|
* Performing GPU resets and controlling persistence mode
|
|
* Reporting or controlling ECC status on supported data center GPUs
|
|
|
|
Output can be formatted as human-readable text or JSON (``--json``), commonly
|
|
used for integration into automated monitoring pipelines.
|
|
|
|
Basic usage examples:
|
|
|
|
.. code-block:: shell
|
|
|
|
# Display GPU information
|
|
amd-smi static
|
|
|
|
# Monitor GPU utilization and metrics
|
|
amd-smi metric --usage
|
|
|
|
# Show detailed information in JSON format
|
|
amd-smi static --json
|
|
|
|
Typical workflow
|
|
----------------
|
|
|
|
For comprehensive ROCm performance analysis:
|
|
|
|
1. Use ``rocprofv3`` to collect profiling data and generate traces
|
|
2. Use ROCprof Compute Viewer for detailed kernel performance analysis and
|
|
counter correlation
|
|
3. Use third-party tools like Perfetto to visualize application traces, API
|
|
calls, and timeline behavior
|
|
4. Use ``amd-smi`` to monitor GPU utilization and system health
|
|
5. Iterate between profiling and optimization, verifying improvements with
|
|
each change
|
|
|
|
This multi-tool approach provides kernel-level metrics, application-level
|
|
tracing, system monitoring, and visual context to understand overall
|
|
performance behavior.
|
|
|
|
.. _parallel execution:
|
|
|
|
Parallel execution
|
|
==================
|
|
|
|
For optimal use and to keep all system components busy, the application must
|
|
reveal and efficiently provide as much parallelism as possible.
|
|
|
|
Application level
|
|
-----------------
|
|
|
|
To enable parallel execution across the host and devices:
|
|
|
|
* Use :ref:`asynchronous calls and streams <asynchronous_how-to>`
|
|
* Assign serial workloads to the host
|
|
* Assign parallel workloads to the devices
|
|
|
|
For parallel workloads:
|
|
|
|
* Use :cpp:func:`__syncthreads()` (see :ref:`synchronization_functions`) for
|
|
intra-block synchronization
|
|
* Use global memory with separate kernel invocations for inter-block
|
|
synchronization (has overhead, minimize when possible)
|
|
|
|
Device level
|
|
------------
|
|
|
|
Maximize parallel execution across multiprocessors:
|
|
|
|
* Execute multiple kernels concurrently on a device
|
|
* Use streams to overlap computation and data transfers
|
|
* Keep all multiprocessors busy with enough concurrent kernels
|
|
* Avoid launching too many kernels (causes resource contention)
|
|
|
|
Multiprocessor level
|
|
--------------------
|
|
|
|
Maximize parallel execution within each :ref:`compute unit <compute_unit>`:
|
|
|
|
* Ensure sufficient resident :ref:`warps <wavefront>` for every clock cycle
|
|
* Exploit instruction-level parallelism within warps
|
|
* Exploit thread-level parallelism across warps
|
|
* Balance resource usage for optimal :ref:`occupancy <occupancy>`
|
|
|
|
.. _memory optimization:
|
|
|
|
Memory throughput optimization
|
|
==============================
|
|
|
|
The first step in maximizing memory throughput is to minimize low-bandwidth
|
|
data transfers between the host and the device.
|
|
|
|
Additionally, maximize the use of on-chip memory (shared memory and caches) and
|
|
minimize transfers with global memory.
|
|
|
|
.. _data transfer:
|
|
|
|
Data transfer optimization
|
|
--------------------------
|
|
|
|
**Minimize host-device transfers**
|
|
|
|
* Move computations from host to device when possible
|
|
* Create, use, and discard intermediate data structures on device
|
|
* Avoid unnecessary copies to host memory
|
|
|
|
**Batch small transfers**
|
|
|
|
Each memory transfer incurs a fixed overhead from driver calls and PCIe
|
|
transaction setup. Consolidating many small transfers into a single large
|
|
transfer amortizes this overhead across more data, resulting in much higher
|
|
effective bandwidth.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Instead of many small transfers
|
|
for (int i = 0; i < n; i++) {
|
|
hipMemcpy(&d_data[i], &h_data[i], sizeof(float), ...);
|
|
}
|
|
|
|
// Use a single large transfer
|
|
hipMemcpy(d_data, h_data, n * sizeof(float), ...);
|
|
|
|
**Use page-locked memory for transfers**
|
|
|
|
Page-locked (pinned) memory cannot be swapped to disk by the operating system,
|
|
allowing the GPU to access it directly via DMA without CPU involvement. This
|
|
eliminates an extra copy through a staging buffer and achieves higher bandwidth.
|
|
|
|
.. code-block:: cuda
|
|
|
|
float* h_pinned;
|
|
hipHostMalloc(&h_pinned, size);
|
|
// Faster transfers than pageable memory
|
|
hipMemcpy(d_data, h_pinned, size, hipMemcpyHostToDevice);
|
|
|
|
**Use mapped memory on integrated systems**
|
|
|
|
On integrated GPUs (APUs), the CPU and GPU share the same physical memory.
|
|
Mapped page-locked memory allows zero-copy access, where the GPU reads directly
|
|
from host memory without requiring an explicit transfer, eliminating redundant
|
|
copies.
|
|
|
|
.. code-block:: cuda
|
|
|
|
int integrated;
|
|
hipDeviceGetAttribute(&integrated, hipDeviceAttributeIntegrated, device);
|
|
if (integrated) {
|
|
// Use mapped page-locked memory - no explicit copy needed
|
|
hipHostMalloc(&ptr, size, hipHostMallocMapped);
|
|
}
|
|
|
|
.. _device memory access:
|
|
|
|
Device memory access
|
|
--------------------
|
|
|
|
**Ensure proper alignment**
|
|
|
|
Memory hardware loads data in aligned chunks (typically 128 bytes). Using
|
|
naturally aligned data types ensures each access maps to a single memory
|
|
transaction, maximizing bandwidth and avoiding split transactions.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Use naturally aligned types
|
|
float4 data; // 16-byte aligned
|
|
float2 data; // 8-byte aligned
|
|
|
|
// Ensure structure alignment
|
|
struct __align__(16) MyStruct {
|
|
float4 data;
|
|
};
|
|
|
|
**Optimize 2D array access**
|
|
|
|
Padding 2D arrays to multiples of the warp size ensures each row starts at an
|
|
aligned memory boundary. This allows consecutive threads accessing the same row
|
|
to generate coalesced memory transactions, thereby maximizing bandwidth.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Ensure array width is multiple of warp size
|
|
int width = ((actual_width + warpSize - 1) / warpSize) * warpSize;
|
|
hipMalloc(&array, width * height * sizeof(float));
|
|
|
|
// Access pattern
|
|
int idx = x + width * y; // width should be warp-aligned
|
|
|
|
**Coalesce memory accesses**
|
|
|
|
When consecutive threads in a warp access consecutive memory addresses, the
|
|
hardware combines these into a single wide transaction. Non-coalesced patterns
|
|
require multiple transactions, reducing effective bandwidth.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Good: consecutive threads access consecutive addresses
|
|
int idx = threadIdx.x + blockIdx.x * blockDim.x;
|
|
data[idx] = value;
|
|
|
|
// Bad: strided access
|
|
int idx = threadIdx.x * stride; // Non-coalesced if stride > 1
|
|
data[idx] = value;
|
|
|
|
For understanding memory coalescing theory, see :ref:`memory_hierarchy_theory`.
|
|
|
|
**Use shared memory for data reuse**
|
|
|
|
Shared memory (:ref:`LDS <lds>`) provides fast on-CU scratchpad memory for
|
|
communication between threads in a block. Loading data into shared memory once
|
|
and reusing it many times reduces global memory traffic, particularly effective
|
|
for tiled algorithms such as matrix multiplication.
|
|
|
|
.. code-block:: cuda
|
|
|
|
__global__ void optimized_kernel(float* input, float* output) {
|
|
__shared__ float tile[TILE_SIZE][TILE_SIZE];
|
|
|
|
// Load data into shared memory
|
|
tile[threadIdx.y][threadIdx.x] = input[...];
|
|
__syncthreads();
|
|
|
|
// Reuse data from fast shared memory
|
|
float result = 0;
|
|
for (int i = 0; i < TILE_SIZE; i++) {
|
|
result += tile[threadIdx.y][i] * tile[i][threadIdx.x];
|
|
}
|
|
__syncthreads();
|
|
|
|
output[...] = result;
|
|
}
|
|
|
|
**Avoid bank conflicts in shared memory**
|
|
|
|
Shared memory is organized into banks, each capable of servicing one request
|
|
per cycle. When multiple threads in a :ref:`warp <wavefront>` access the
|
|
same bank simultaneously, the requests are serialized, reducing throughput.
|
|
Padding arrays by one element shifts addresses to avoid systematic conflicts.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Bad: power-of-2 stride causes conflicts
|
|
__shared__ float data[32][32];
|
|
float value = data[threadIdx.x][threadIdx.y];
|
|
|
|
// Good: padding avoids conflicts
|
|
__shared__ float data[32][33]; // Extra column
|
|
float value = data[threadIdx.x][threadIdx.y];
|
|
|
|
For bank conflict theory, see :ref:`bank_conflicts_theory`.
|
|
|
|
**Use texture memory for 2D spatial access**
|
|
|
|
Texture memory provides hardware-accelerated 2D filtering and caching optimized
|
|
for spatial locality. It automatically handles boundary conditions and can
|
|
interpolate values, making it ideal for image processing and nearby-neighbor
|
|
access patterns.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Create texture object
|
|
hipTextureObject_t texObj;
|
|
hipCreateTextureObject(&texObj, &resDesc, &texDesc, NULL);
|
|
|
|
// Access in kernel
|
|
float value = tex2D<float>(texObj, x, y);
|
|
|
|
.. _instruction optimization:
|
|
|
|
Instruction throughput optimization
|
|
====================================
|
|
|
|
Arithmetic instructions
|
|
-----------------------
|
|
|
|
**Use efficient operations**
|
|
|
|
Division requires many more hardware cycles than multiplication. Similarly,
|
|
bitwise operations (shifts, AND, OR) are single-cycle instructions on integer
|
|
units, making them far more efficient than equivalent arithmetic for
|
|
power-of-two calculations.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Prefer multiplication over division
|
|
float result = value * 0.5f; // Fast
|
|
float result = value / 2.0f; // Slower
|
|
|
|
// Use bitwise operations for powers of 2
|
|
int index = threadIdx.x << 2; // Multiply by 4
|
|
int mask = (1 << n) - 1; // Create bit mask
|
|
|
|
**Use single-precision when possible**
|
|
|
|
AMD GPUs have significantly higher throughput for single-precision (FP32)
|
|
operations compared to double-precision (FP64). Using single-precision math
|
|
functions can deliver substantial performance gains when FP64 accuracy is not
|
|
required.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Single-precision (faster)
|
|
float result = sinf(x);
|
|
float result = expf(x);
|
|
|
|
// Double-precision (slower, use only when necessary)
|
|
double result = sin(x);
|
|
double result = exp(x);
|
|
|
|
**Leverage fast math intrinsics**
|
|
|
|
Hardware-specific intrinsics bypass certain accuracy checks and use lookup
|
|
tables or polynomial approximations, trading slight precision loss for
|
|
significantly higher throughput. These should be used when the application can
|
|
tolerate reduced precision.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Fast intrinsic versions
|
|
float ex = __expf(x); // Fast exponential
|
|
float lg = __logf(x); // Fast logarithm
|
|
float sq = __fsqrt_rn(x); // Fast square root
|
|
float rc = __frcp_rn(x); // Fast reciprocal
|
|
|
|
.. _control flow instructions:
|
|
|
|
Control flow optimization
|
|
-------------------------
|
|
|
|
**Minimize divergence**
|
|
|
|
When threads in a warp take different execution paths, the hardware serializes
|
|
both branches, executing each path with only the relevant threads active. This
|
|
reduces effective parallelism and wastes cycles on inactive threads.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Good: no divergence (condition depends on threadIdx)
|
|
if (threadIdx.x < 32) {
|
|
// All threads in first half-warp execute
|
|
}
|
|
|
|
// Bad: divergence within warp
|
|
if (data[threadIdx.x] > threshold) {
|
|
// Some threads execute, others don't
|
|
}
|
|
|
|
**Use branch hints for predictable conditions**
|
|
|
|
Providing hints about branch likelihood helps the compiler generate better
|
|
instruction ordering and can improve the branch predictor's accuracy, reducing
|
|
pipeline stalls when the prediction proves correct.
|
|
|
|
.. code-block:: cuda
|
|
|
|
if (__builtin_expect(rare_condition, 0)) {
|
|
// Unlikely branch
|
|
}
|
|
|
|
// C++20 attribute
|
|
if (common_condition) [[likely]] {
|
|
// Likely branch
|
|
}
|
|
|
|
**Avoid divergent warps**
|
|
|
|
When divergence is unavoidable, restructure the code to separate divergent paths
|
|
into different kernel launches or use predication (branchless programming) to
|
|
keep all threads active, though computing unnecessary values may be acceptable
|
|
if it avoids the serialization penalty.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Instead of:
|
|
if (threadIdx.x % 2 == 0) {
|
|
result = compute_even();
|
|
} else {
|
|
result = compute_odd();
|
|
}
|
|
|
|
// Consider separating into different kernels or using predication
|
|
|
|
Synchronization
|
|
---------------
|
|
|
|
**Use minimal synchronization**
|
|
|
|
Each synchronization point stalls all threads in a block until the slowest one
|
|
reaches the barrier. Minimize synchronizations by carefully analyzing data
|
|
dependencies—only synchronize when threads genuinely need to exchange data
|
|
through shared memory.
|
|
|
|
.. code-block:: cuda
|
|
|
|
__global__ void kernel() {
|
|
__shared__ float data[256];
|
|
|
|
// Load phase
|
|
data[threadIdx.x] = input[...];
|
|
__syncthreads(); // Necessary sync
|
|
|
|
// Compute phase - no sync needed if threads are independent
|
|
float result = compute(data[...]);
|
|
|
|
// Store phase - sync only if needed
|
|
output[...] = result;
|
|
}
|
|
|
|
**Use streams for async execution**
|
|
|
|
Streams enable concurrent execution of independent operations. Commands in
|
|
different streams can overlap in time, allowing kernel execution and memory
|
|
transfers to run simultaneously. This maximizes GPU utilization by keeping
|
|
multiple execution engines busy concurrently.
|
|
|
|
.. code-block:: cuda
|
|
|
|
hipStream_t stream1, stream2;
|
|
hipStreamCreate(&stream1);
|
|
hipStreamCreate(&stream2);
|
|
|
|
// Overlap independent operations
|
|
kernel1<<<grid, block, 0, stream1>>>(...);
|
|
kernel2<<<grid, block, 0, stream2>>>(...);
|
|
|
|
hipStreamSynchronize(stream1);
|
|
hipStreamSynchronize(stream2);
|
|
|
|
Managing register pressure
|
|
==========================
|
|
|
|
High register usage can limit :ref:`occupancy <occupancy>`. Follow these steps:
|
|
|
|
**Minimize live variables**
|
|
|
|
The compiler allocates registers for every variable that must remain accessible.
|
|
Reducing the number of simultaneously live variables frees registers, allowing
|
|
more warps to fit on each CU. Chaining function calls trades some redundant
|
|
computation for lower register usage.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Instead of storing all intermediate results
|
|
float a = compute_a();
|
|
float b = compute_b();
|
|
float c = compute_c();
|
|
float result = combine(a, b, c);
|
|
|
|
// Recompute or chain operations
|
|
float result = combine(compute_a(), compute_b(), compute_c());
|
|
|
|
**Use shared memory for temporary storage**
|
|
|
|
Per-thread arrays stored in registers consume valuable register space, limiting
|
|
:ref:`occupancy <occupancy>`. Moving temporary storage to
|
|
:ref:`shared memory <lds>` trades register usage for shared memory usage, often
|
|
allowing higher occupancy since shared memory limits are typically less
|
|
restrictive.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Instead of per-thread arrays (uses registers)
|
|
float temp[100];
|
|
|
|
// Use shared memory
|
|
__shared__ float temp[blockDim.x][100];
|
|
float* my_temp = temp[threadIdx.x];
|
|
|
|
**Adjust launch bounds**
|
|
|
|
The ``__launch_bounds__`` attribute provides hints to the compiler about
|
|
expected thread block size and minimum blocks per CU. This guides register
|
|
allocation decisions, potentially trading per-thread register count for higher
|
|
occupancy.
|
|
|
|
.. code-block:: cuda
|
|
|
|
__global__ void
|
|
__launch_bounds__(256, 4) // 256 threads, 4 blocks per CU
|
|
my_kernel() {
|
|
// Kernel code
|
|
}
|
|
|
|
**Check register usage during compilation**
|
|
|
|
The compiler can report per-kernel register usage statistics. Monitoring this
|
|
output helps identify kernels consuming excessive registers, guiding
|
|
optimization efforts toward reducing register pressure in the most impactful
|
|
areas.
|
|
|
|
.. code-block:: shell
|
|
|
|
hipcc --resource-usage kernel.hip
|
|
|
|
For register pressure theory, see :ref:`register_pressure_theory`.
|
|
|
|
Improving occupancy
|
|
===================
|
|
|
|
Higher :ref:`occupancy <occupancy>` helps hide latency. Follow these steps:
|
|
|
|
**Reduce register usage per thread**
|
|
|
|
Use techniques from "Managing register pressure" above.
|
|
|
|
**Reduce shared memory usage per block**
|
|
|
|
Each :ref:`CU <compute_unit>` has limited :ref:`shared memory <lds>` that must
|
|
be divided among resident blocks. Reducing per-block shared memory usage allows
|
|
more blocks to reside simultaneously, increasing :ref:`occupancy <occupancy>`
|
|
and improving latency hiding through greater thread-level parallelism.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Allocate only what's needed
|
|
__shared__ float tile[TILE_SIZE][TILE_SIZE];
|
|
|
|
// Or use dynamic allocation
|
|
extern __shared__ float dynamic_shared[];
|
|
|
|
**Optimize block size**
|
|
|
|
AMD Instinct GPUs execute threads in :ref:`warps <wavefront>` of 64, while
|
|
AMD Radeon GPUs execute threads in warps of 32. Choosing
|
|
block sizes as multiples of 64 or 32 prevents partial warps that waste
|
|
execution slots. Larger blocks (128-256 threads) typically achieve better
|
|
:ref:`occupancy <occupancy>` and resource utilization.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Use multiples of warp size
|
|
dim3 block(64); // Good for AMD Instinct GPUs (warp=64)
|
|
dim3 block(128); // Common choice
|
|
dim3 block(256); // Good for high-occupancy kernels
|
|
|
|
// Avoid very small blocks
|
|
dim3 block(32); // May waste resources on Instinct GPUs
|
|
|
|
**Profile occupancy**
|
|
|
|
Profiling tools report the ratio of active :ref:`warps <wavefront>` to
|
|
maximum possible warps per :ref:`CU <compute_unit>`. Low
|
|
:ref:`occupancy <occupancy>` suggests resource constraints (registers or shared
|
|
memory) are limiting parallelism and may indicate opportunities for
|
|
optimization.
|
|
|
|
.. code-block:: shell
|
|
|
|
rocprofv3 --occupancy ./your_application
|
|
|
|
For occupancy theory, see :ref:`occupancy`.
|
|
|
|
Minimizing memory thrashing
|
|
============================
|
|
|
|
Applications frequently allocating and freeing memory might experience slower
|
|
allocation calls over time. To optimize:
|
|
|
|
**Allocate early, deallocate late**
|
|
|
|
Frequent allocation and deallocation causes memory fragmentation and increases
|
|
allocator overhead. Reusing allocations across iterations amortizes the cost
|
|
of memory management and maintains better memory locality.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Bad: frequent allocation in loop
|
|
for (int i = 0; i < iterations; i++) {
|
|
float* temp;
|
|
hipMalloc(&temp, size);
|
|
// Use temp
|
|
hipFree(temp);
|
|
}
|
|
|
|
// Good: allocate once
|
|
float* temp;
|
|
hipMalloc(&temp, size);
|
|
for (int i = 0; i < iterations; i++) {
|
|
// Reuse temp
|
|
}
|
|
hipFree(temp);
|
|
|
|
**Avoid allocating all available memory**
|
|
|
|
Reserving some memory headroom prevents allocation failures and system
|
|
instability. The driver and runtime need workspace for internal operations, and
|
|
leaving a safety margin ensures stable operation without unexpected
|
|
out-of-memory errors.
|
|
|
|
.. code-block:: cuda
|
|
|
|
std::size_t free, total;
|
|
hipMemGetInfo(&free, &total);
|
|
|
|
// Don't allocate all free memory
|
|
std::size_t safe_size = free * 0.9; // Leave some margin
|
|
|
|
**Use managed memory for oversubscription**
|
|
|
|
Managed memory automatically migrates data between host and device on demand,
|
|
allowing allocations larger than physical GPU memory. Prefetching hints help
|
|
the runtime optimize page placement, reducing migration overhead during kernel
|
|
execution.
|
|
|
|
.. code-block:: cuda
|
|
|
|
// Allows exceeding physical memory
|
|
float* data;
|
|
hipMallocManaged(&data, large_size);
|
|
|
|
// Optionally prefetch to device
|
|
hipMemPrefetchAsync(data, size, device, stream);
|
|
|
|
Summary
|
|
=======
|
|
|
|
Key optimization techniques:
|
|
|
|
* **Profile first**: Use ``rocprofv3`` to identify actual bottlenecks
|
|
* **Parallelize effectively**: Maximize work at all levels (application, device,
|
|
CU)
|
|
* **Optimize memory**: Minimize transfers, maximize coalescing, use LDS
|
|
* **Manage resources**: Balance registers, shared memory, and occupancy
|
|
* **Minimize divergence**: Structure control flow to keep
|
|
:ref:`warps <wavefront>` coherent
|
|
|
|
For understanding the theory behind these techniques, refer to
|
|
:doc:`../understand/performance_optimization` and
|
|
:doc:`../understand/hardware_implementation`.
|