Shared-memory bank conflicts
32 banks, one word each per cycle: why a column read serialises 32 ways, and how one word of padding fixes it.
Loading the animation…
Concept
Shared memory is split into 32 banks: "successive 32-bit words map to successive banks. Each bank has a bandwidth of 32 bits per clock cycle" (CUDA Programming Guide §2.3.4.2). Word lives in bank , which is why the animation draws memory as rows of 32 words: a word's column is its bank.
If the 32 lanes of a warp ask for words in 32 different banks, one pass serves them all. If several lanes ask a bank for different words, the bank serves them one per pass: a bank conflict, and "the access to the data in that bank will be serialized". If they ask for the same word, it is broadcast to all of them at no extra cost.
The default is the guide's own example. A 32 × 32 tile of floats, float s[32][32], read down a column (lane reads s[i][0], as a transpose does): every word sits in column 0, so all 32 requests queue at bank 0 and the warp needs 32 passes, a 32-way conflict. Now move the padding slider to 1, making the array s[32][33]: each row is one word longer, so row 's first word moves to bank , the column becomes a diagonal, and the conflict disappears. The cost is 32 unused words.
Other patterns to try: a row read is conflict-free; a stride of 2 words gives the guide's two-way conflict, while stride 3, or any odd stride, is conflict-free; and broadcast has eight lanes share each word, still one pass.
Maths
Lane reads word of bank . Bank must deliver every distinct word asked of it, one per pass, so the warp needs
and gets of shared memory's bandwidth.
For a strided access , lanes and collide when . The 32 lanes fall into lanes per bank, so
which is 1 for every odd stride, 2 for and 32 for .
For the column of float s[32][32 + p], , so : the stride is effectively . With every lane hits bank 0 (32 passes); with lane hits bank (1 pass); with , two passes.
Code
The model's pass count, cut from src/lib/gpu/model.ts: a lane is served in the pass whose index is its word's place in its bank's queue.
for (let pnum = 0; pnum < degree; pnum++) {
const served: number[] = [];
words.forEach((w, lane) => {
const d = distinct[w % banks] as number[];
if (d.length > pnum && d[pnum] === w) served.push(lane);
});
The fix in CUDA, illustrative and not compiled by this site's CI, as the guide shows it:
__shared__ float tile[32][32 + 1]; // one word of padding per row
tile[threadIdx.y][threadIdx.x] = in[...]; // row write: no conflict
__syncthreads();
out[...] = tile[threadIdx.x][threadIdx.y]; // column read: no conflict either