Introduction
In our last lesson, we delved into the fine-grained architecture of shared memory, discovering how an access stride that is a multiple of 32 can lead to severe bank conflicts, serializing memory requests and crippling performance. By padding our shared memory array, we changed the stride and restored parallel access, reinforcing a critical lesson: on a GPU, your data access pattern is paramount.
Today, we will expand our memory optimization toolkit by exploring two more specialized, read-only memory paths: constant memory and the read-only data cache. While global memory is versatile, it is not always the most efficient path for fetching data. CUDA provides these alternative pathways, each with its own cache and hardware optimizations tailored to specific access patterns. Mastering them is key to extracting maximum performance, especially when dealing with data that is read frequently but never modified by a kernel.
The GPU Memory Hierarchy: Specialized Paths for Read-Only Data
As we've discussed, the GPU features a complex memory hierarchy, with different levels offering trade-offs between size, speed, and flexibility. Registers and shared memory provide the highest on-chip bandwidth, but are limited in capacity and scope. Global memory (DRAM) is large, but has high latency.
Between these extremes lie specialized caches designed to accelerate read-only access. The following video provides a high-level overview of the different memory types, including constant memory.
Memory Hierarchy | GPU Programming | Episode 6
This video from Simon Oz on YouTube gives a brief tour of the GPU memory hierarchy, which will help us place constant and texture memory in context.
Please watch the introduction from the beginning (the overview) to refresh your understanding of the on-chip vs. off-chip distinction. Then, watch the specific segments on constant memory and the final summary, which reiterates its key properties.
The diagram below illustrates where these caches fit. Notice that within each Streaming Multiprocessor (SM), alongside the L1 cache and shared memory (L1/SMEM), there is a distinct path for read-only data. This isn't an accident; it's a deliberate architectural choice to optimize common read patterns that don't fit the model of general-purpose L1 caching.

Let's investigate the two most important read-only optimizations: constant memory and the read-only cache (often called the texture cache).
Constant Memory: The Broadcast Specialist
Constant memory is a small, 64 KB region of device memory that is cached on-chip. Its design is optimized for one specific, but very common, situation: when all threads in a warp need to read the exact same memory address.
The Broadcast Mechanism
When all 32 threads in a warp issue a read request for the same address in constant memory, the hardware is intelligent enough to detect this. It fetches the data from the constant cache once and then broadcasts the value to all 32 threads in a single cycle. This makes the access as fast as reading a register.
This is extremely efficient for:
- Kernel-wide parameters: Configuration values, scaling factors, or pointers passed to a kernel.
- Physical constants: Values like or gravitational constants used in scientific simulations.
- Filter coefficients: Small arrays, like a 3x3 convolution filter, where the same coefficients are used by many threads working on different data points.
The Pitfall: Serialization
The broadcast mechanism is the source of constant memory's power, but also its greatest weakness. If threads within a warp access different addresses in constant memory, the hardware can no longer perform a broadcast. Instead, it serializes the memory requests. The warp must re-issue the read instruction for each unique address requested by its threads.
If two threads access two different addresses, the bandwidth is halved. If all 32 threads access 32 different addresses, the accesses become completely serial, resulting in a 32x performance penalty. This makes constant memory a terrible choice for data that is indexed per-thread, like my_array[threadIdx.x].
The following resource provides an excellent and concise explanation of this critical trade-off.
The CUDA Memory Hierarchy: Architectural Analysis, Performance ...
This article from Uplatz offers a clear, architecturally-grounded explanation of constant memory's behavior.
Read the section titled Optimized Broadcasts. Pay close attention to the distinction between the "Broadcast" and "Serialization" mechanisms.
This behavior is confirmed by the official CUDA C++ Best Practices Guide.
CUDA C++ Best Practices Guide 13.1 documentation
The NVIDIA documentation provides the definitive statement on constant memory performance.
Please read the section on Constant Memory. The key sentence is: "Accesses to different addresses by threads within a warp are serialized, thus the cost scales linearly with the number of unique addresses read by all threads within a warp."
To see the dramatic real-world impact of this, let's watch a video that benchmarks these exact scenarios.
Constant Memory | GPU Programming
This video by Simon Oz on YouTube is dedicated to constant memory and provides clear performance graphs.
Watch the initial recap from the introduction. Then, focus on the explanation of the two benefits from broadcast and caching. Finally, observe the benchmark results. First, see the performance gain when all threads access the same location (uniform access). Then, watch how performance collapses when threads access different locations (divergent access). The 18x slowdown in the second benchmark is a powerful illustration of serialization.
The Read-Only Cache (Texture Memory): Spatial Locality Expert
The second specialized read path is the read-only data cache, often referred to as texture memory due to its origins in graphics processing. While modern GPUs have unified L1 and texture caches, the texture path still provides unique hardware capabilities optimized for spatial locality, particularly in 2D and 3D.
Beyond Linear Coalescing
As you know, global memory access is fastest when it is coalesced—meaning threads in a warp access a contiguous, 128-byte aligned block of memory. This is a 1D, linear optimization. But what if your access pattern is not linear? Consider a stencil operation where each thread needs to read its neighbors in a 2D grid: (x, y), (x+1, y), (x-1, y), (x, y+1), and (x, y-1). These accesses are spatially close but are not contiguous in memory, leading to poor global memory performance.
The texture cache is designed for this exact scenario. It uses a different memory layout (often a space-filling curve like a Z-order curve) to map 2D coordinates to memory addresses such that elements that are close in 2D are also close in linear memory. This dramatically increases the cache hit rate for spatially local, non-linear access patterns.
Hardware "Freebies"
Because this hardware originated from texturing 3D models, it includes several powerful features that can be co-opted for general-purpose computing, often at zero instruction cost:
- Boundary Handling: If a thread tries to read outside an array's bounds (e.g., pixel
[-1, 5]), you would normally needifstatements to handle it. The texture hardware can do this automatically withclamp(snap to edge) orwrap(modulo arithmetic) modes. - Hardware Interpolation: The texture units can perform fast, low-precision linear interpolation between neighboring data points. If you request data at coordinate
(10.5, 20.2), the hardware can automatically return a blended value from the four surrounding pixels, which is invaluable in image processing and some scientific visualizations.
The following resource gives a fantastic breakdown of these capabilities.
The CUDA Memory Hierarchy: Architectural Analysis, Performance ...
We return to the Uplatz article for its excellent description of texture memory.
Read the section Texture Memory. Focus on understanding how its specialized memory layout improves cache hits for 2D access patterns and what hardware features like addressing modes and interpolation offer.
Summary: Choosing the Right Read Path
You now have three primary paths for reading data from device memory, each optimized for a different pattern:
| Memory Path | Optimal Access Pattern | Key Benefit |
|---|---|---|
| Global Memory (via L1/L2) | Coalesced: Threads in a warp access a contiguous 1D block of memory. | High bandwidth for linear, sequential reads. |
| Constant Memory | Uniform/Broadcast: All threads in a warp access the same address. | Extremely fast, single-cycle broadcast. |
| Texture Memory (Read-only) | Spatially Local: Threads in a warp access addresses that are near each other in 2D/3D space. | High cache hit rate for non-linear neighborhood access; free hardware features. |
Choosing the correct memory path is a core task of a kernel performance engineer. If your data is read-only, ask yourself about the access pattern: Is it the same for all threads? Use constant memory. Is it spatially local but not linear? Use the texture path. Is it linear and coalesced? Regular global memory access is likely best.
Conclusion
In this lesson, we've added two powerful, specialized tools to your CUDA optimization arsenal. You've learned that for read-only data, you can often achieve significant speedups by moving beyond the default global memory path.
Key Takeaways:
- Constant memory is a small, cached space ideal for data that is uniform across a warp. It provides an extremely fast broadcast mechanism but suffers from severe performance degradation from serialization if threads access different addresses.
- The read-only data cache (texture memory) is optimized for 2D/3D spatial locality. It excels where global memory's linear coalescing model fails, such as in stencil or image processing codes.
- The texture path also provides "free" hardware features like boundary handling and linear interpolation, offloading work from the compute pipeline.
- As a performance engineer, you must analyze your kernel's data access patterns to select the optimal memory path: global for coalesced, constant for uniform, and texture for spatially local.
In our next lesson, we will shift our focus from memory access back to the threads themselves. We will examine how a warp executes instructions in a Single Instruction, Multiple Thread (SIMT) model. This will lay the groundwork for understanding thread divergence, another critical concept in CUDA performance.