GPU Architecture · Code example

02-syncthreads-reverse.cu

⤓ raw .cu (compilable)

Reverses each block's segment through shared memory, which only works because of __syncthreads(); a -DSKIP_SYNC build plus compute-sanitizer racecheck expose the race when the barrier is removed.

// 02-syncthreads-reverse.cu — see why __syncthreads() is required.
//
// Each block loads its segment into shared memory, then writes it back reversed.
// The reversed write reads a *neighbor's* shared slot, so it must not run until
// the whole tile is loaded — that's the barrier's job.
//
//   Build (correct):        nvcc -arch=sm_120 02-syncthreads-reverse.cu -o rev
//   Build (barrier removed): nvcc -arch=sm_120 -DSKIP_SYNC 02-syncthreads-reverse.cu -o rev_bad
//   Run:                    ./rev      (expect: PASS)
//                           ./rev_bad  (may PASS by luck or FAIL — a data race is UB)
//
// Tip: run either under  compute-sanitizer --tool synccheck ./rev   (barrier checks)
//                   or   compute-sanitizer --tool racecheck ./rev_bad (shared races)

#include <cstdio>
#include <cstdlib>
#include <cuda_runtime.h>

#define CHECK(call) do {                                              \
    cudaError_t _e = (call);                                          \
    if (_e != cudaSuccess) {                                          \
      printf("CUDA error at line %d: %s\n", __LINE__,                 \
             cudaGetErrorString(_e));                                 \
      return 1;                                                       \
    }                                                                 \
  } while (0)

#define BLOCK 256

// Reverse each block's own segment of `n` elements via shared memory.
__global__ void reverseBlock(float* out, const float* in, int n) {
  __shared__ float tile[BLOCK];
  int t          = threadIdx.x;
  int blockStart = blockIdx.x * blockDim.x;
  int gid        = blockStart + t;

  if (gid < n) tile[t] = in[gid];          // each thread writes ITS slot

#ifndef SKIP_SYNC
  __syncthreads();                         // wait until the whole tile is written…
#endif

  int rev = blockDim.x - 1 - t;            // …because now we read a NEIGHBOR's slot
  if (gid < n) out[gid] = tile[rev];
}

int main() {
  const int n = 64 * BLOCK;                // exact multiple of BLOCK → no partial blocks
  size_t bytes = (size_t)n * sizeof(float);

  float *hin = (float*)malloc(bytes), *hout = (float*)malloc(bytes);
  for (int i = 0; i < n; ++i) hin[i] = (float)i;

  float *din, *dout;
  CHECK(cudaMalloc(&din, bytes));
  CHECK(cudaMalloc(&dout, bytes));
  CHECK(cudaMemcpy(din, hin, bytes, cudaMemcpyHostToDevice));

  reverseBlock<<<n / BLOCK, BLOCK>>>(dout, din, n);
  CHECK(cudaGetLastError());
  CHECK(cudaDeviceSynchronize());

  CHECK(cudaMemcpy(hout, dout, bytes, cudaMemcpyDeviceToHost));

  // Verify: within each block, out[blockStart + t] == in[blockStart + (BLOCK-1-t)].
  int bad = 0;
  for (int b = 0; b < n / BLOCK; ++b)
    for (int t = 0; t < BLOCK; ++t) {
      float expect = hin[b * BLOCK + (BLOCK - 1 - t)];
      if (hout[b * BLOCK + t] != expect) { bad++; }
    }
  printf("%s  (%d mismatches out of %d)\n", bad == 0 ? "PASS" : "FAIL", bad, n);

  free(hin); free(hout);
  cudaFree(din); cudaFree(dout);
  return 0;
}
⌂ Home Glossary →