CUDA 13.4 samples update - v13.4-public

Release 13.4 of the CUDA Samples supported by CUDA Toolkit 13.4.
See Changelog for more information.
This commit is contained in:
Dheemanth
2026-09-09 15:07:08 -07:00
committed by GitHub
parent b7c5481c55
commit 5443602d89
871 changed files with 6843 additions and 321849 deletions

View File

@@ -1,18 +0,0 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/../../../Common"
],
"defines": [],
"compilerPath": "/usr/local/cuda/bin/nvcc",
"cStandard": "gnu17",
"cppStandard": "gnu++14",
"intelliSenseMode": "linux-gcc-x64",
"configurationProvider": "ms-vscode.makefile-tools"
}
],
"version": 4
}

View File

@@ -1,7 +0,0 @@
{
"recommendations": [
"nvidia.nsight-vscode-edition",
"ms-vscode.cpptools",
"ms-vscode.makefile-tools"
]
}

View File

@@ -1,35 +1,25 @@
cmake_minimum_required(VERSION 3.20)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/Modules")
include("${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/DetectCudaArch.cmake")
if(SAMPLE_SKIP_BUILD)
return()
endif()
project(simpleAtomicIntrinsics LANGUAGES C CXX CUDA)
include("${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/CudaSampleCommon.cmake")
find_package(CUDAToolkit REQUIRED)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CUDA_ARCHITECTURES 75 80 86 87 89 90 100 110 120)
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Wno-deprecated-gpu-targets")
if(ENABLE_CUDA_DEBUG)
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -G") # enable cuda-gdb (may significantly affect performance on some targets)
else()
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -lineinfo") # add line information to all builds for debug tools (exclusive to -G option)
endif()
# Include directories and libraries
include_directories(../../../Common)
# Source file
# Add target for simpleAtomicIntrinsics
add_executable(simpleAtomicIntrinsics simpleAtomicIntrinsics.cu simpleAtomicIntrinsics_cpu.cpp)
add_executable(simpleAtomicIntrinsics simpleAtomicIntrinsics.cu)
target_compile_options(simpleAtomicIntrinsics PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:--extended-lambda>)
target_compile_features(simpleAtomicIntrinsics PRIVATE cxx_std_17 cuda_std_17)
set_target_properties(simpleAtomicIntrinsics PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
# Include installation configuration
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/InstallSamples.cmake)
include(InstallSamples)
setup_samples_install()

View File

@@ -1,32 +1,123 @@
# simpleAtomicIntrinsics - Simple Atomic Intrinsics
# simpleAtomicIntrinsics - Atomic vs. Non-Atomic Operations
## Description
A simple demonstration of global memory atomic instructions.
A CUDA sample that demonstrates **why atomic operations are needed** by running the same work with and without atomics under heavy contention. 1,000,000 threads write into an array of just 10 integers, so roughly 100,000 threads collide on every element. Three operations — add, max, and compare-and-swap — are each shown four ways: a non-atomic kernel, the CUDA atomic intrinsic, `cuda::std::atomic_ref` from CCCL (the `std::atomic` API in device code), and `cuda::atomic_ref` from CCCL (the CUDA-specific variant that natively supports thread scopes):
Both CCCL variants use `atomic_ref` rather than `atomic` so they act on the *existing* plain-`int` array — this matches the behavior of the intrinsic atomics, which also adds atomic access to memory that already exists. `atomic<int>` would instead require the array elements themselves to be declared separately as the atomic type.
| Operation | Non-atomic | Intrinsic | cuda::std::atomic_ref | cuda::atomic_ref |
|---|---|---|---|---|
| Add 1 to an element | `increment` | `increment_atomic` | `increment_atomic_std` | `increment_atomic_cuda` |
| Keep the maximum value | `max` | `max_atomic` | `max_atomic_std` | `max_atomic_cuda` |
| Increment via compare-and-swap | `cas` | `cas_atomic` | `cas_atomic_std` | `cas_atomic_cuda` |
The non-atomic kernels perform the read-modify-write as separate steps, so concurrent threads interleave and lose updates. The atomic kernels perform it as one indivisible hardware operation and produce the exact expected result every time.
## What You'll Learn
- What a race condition looks like: the non-atomic results are dramatically (add, CAS) or subtly (max) wrong
- Using the atomic intrinsics `atomicAdd`, `atomicMax`, and `atomicCAS` on global memory
- Building an atomic operation from an `atomicCAS` retry loop — the pattern that can implement any read-modify-write atomically
- Using `cuda::std::atomic_ref` (libcu++/CCCL) to write the same atomics with the standard `std::atomic` API in device code
- Using `cuda::atomic_ref` (CCCL) — the CUDA-specific variant that shares the same API as `cuda::std::atomic_ref` but natively supports CUDA thread scopes
- Why dedicated intrinsics beat CAS loops under contention (compare the `cas_atomic` timing against `increment_atomic`)
- Timing GPU work with CUDA events (`cudaEventRecord` / `cudaEventElapsedTime`)
- Resetting device buffers between runs with `cudaMemset`
## Key Concepts
Atomic Intrinsics
- **Race condition** — a plain `g[i] = g[i] + 1` is three steps (read, modify, write); two threads can read the same old value and one increment is lost
- **Atomic read-modify-write** — the hardware serializes atomic updates to the same address (performed at the L2 cache), so no update is lost
- **Compare-and-swap (CAS)** — `atomicCAS(addr, expected, desired)` swaps only if the current value equals `expected` and returns the value it found; looping until the swap succeeds makes any operation atomic
- **Contention cost** — atomics are correct but serialize colliding threads; the CAS retry loop shows this at its most extreme
## Supported SM Architectures
## Key APIs
[SM 5.0 ](https://developer.nvidia.com/cuda-gpus) [SM 5.2 ](https://developer.nvidia.com/cuda-gpus) [SM 5.3 ](https://developer.nvidia.com/cuda-gpus) [SM 6.0 ](https://developer.nvidia.com/cuda-gpus) [SM 6.1 ](https://developer.nvidia.com/cuda-gpus) [SM 7.0 ](https://developer.nvidia.com/cuda-gpus) [SM 7.2 ](https://developer.nvidia.com/cuda-gpus) [SM 7.5 ](https://developer.nvidia.com/cuda-gpus) [SM 8.0 ](https://developer.nvidia.com/cuda-gpus) [SM 8.6 ](https://developer.nvidia.com/cuda-gpus) [SM 8.7 ](https://developer.nvidia.com/cuda-gpus) [SM 8.9 ](https://developer.nvidia.com/cuda-gpus) [SM 9.0 ](https://developer.nvidia.com/cuda-gpus)
### CUDA Device Intrinsics
- `atomicAdd` — atomically add a value to a memory location
- `atomicMax` — atomically store the maximum of the current and a proposed value
- `atomicCAS` — atomically compare-and-swap; returns the previous value
## Supported OSes
### libcu++ (CCCL)
- `cuda::std::atomic_ref<int>` — wraps plain memory with the standard `std::atomic` interface, usable in device code; header: `<cuda/std/atomic>`
- `cuda::atomic_ref<int>` — CUDA-specific version of `std::atomic_ref`; same API but natively supports CUDA thread scopes (e.g. `cuda::thread_scope_device`); header: `<cuda/atomic>`
- `fetch_add` — atomic add, `std::atomic` style
- `load` / `compare_exchange_weak` — the standard CAS retry loop; used to build max (which `std::atomic` lacks) and the CAS increment
Linux, Windows, QNX
### CUDA Runtime
- `cudaMalloc` / `cudaFree` — allocate and release device memory
- `cudaMemset` — fill device memory with a byte value (zero the array between runs)
- `cudaMemcpy` — copy results back to the host
- `cudaEventCreate` / `cudaEventRecord` / `cudaEventSynchronize` / `cudaEventElapsedTime` / `cudaEventDestroy` — GPU-timeline timing of each kernel
## Supported CPU Architecture
## Requirements
x86_64, armv7l, aarch64
### Hardware
- NVIDIA GPU with Compute Capability 7.5 or higher
## CUDA APIs involved
### Software
- CUDA Toolkit
- CMake 3.20 or newer
- A C++17-capable host compiler
### [CUDA Runtime API](http://docs.nvidia.com/cuda/cuda-runtime-api/index.html)
cudaStreamCreateWithFlags, cudaFree, cudaMallocHost, cudaFreeHost, cudaStreamSynchronize, cudaMalloc, cudaMemcpyAsync
## How to Build
## Prerequisites
See the [top-level README](../../../README.md#building-cuda-samples) for full build instructions, including how to build all samples or a single sample standalone.
Download and install the [CUDA Toolkit](https://developer.nvidia.com/cuda-downloads) for your corresponding platform.
## How to Run
## References (for more details)
```bash
./simpleAtomicIntrinsics
```
No command-line arguments are required. The sample always runs on device 0.
## Expected Output
```text
=== Atomic vs. non-atomic operations (intrinsics, cuda::std::atomic_ref, cuda::atomic_ref) ===
GPU Device 0: with compute capability X.Y and Number of SMs <smCount>
1000000 total threads in 1000 blocks writing into 10 array elements
[add] expected: every element = 100000
non-atomic (0.19 ms): { 13 13 13 13 13 13 13 13 13 13 }
atomicAdd() (0.19 ms): { 100000 100000 100000 100000 100000 100000 100000 100000 100000 100000 }
cuda::std::atomic_ref::fetch_add() (0.26 ms): { 100000 100000 100000 100000 100000 100000 100000 100000 100000 100000 }
cuda::atomic_ref::fetch_add() (0.26 ms): { 100000 100000 100000 100000 100000 100000 100000 100000 100000 100000 }
[max] expected: element i = 999990 + i
non-atomic (0.013 ms): { 998070 998071 998072 998073 998064 998065 998066 998067 998068 998069 }
atomicMax() (0.18 ms): { 999990 999991 999992 999993 999994 999995 999996 999997 999998 999999 }
cuda::std::atomic_ref::compare_exchange_weak() (1.0 ms): { 999990 999991 999992 999993 999994 999995 999996 999997 999998 999999 }
cuda::atomic_ref::compare_exchange_weak() (1.0 ms): { 999990 999991 999992 999993 999994 999995 999996 999997 999998 999999 }
[CAS] expected: every element = 100000
non-atomic (0.014 ms): { 13 13 13 13 13 13 13 13 13 13 }
atomicCAS() (1716 ms): { 100000 100000 100000 100000 100000 100000 100000 100000 100000 100000 }
cuda::std::atomic_ref::compare_exchange_weak() (9934 ms): { 100000 100000 100000 100000 100000 100000 100000 100000 100000 100000 }
cuda::atomic_ref::compare_exchange_weak() (9934 ms): { 100000 100000 100000 100000 100000 100000 100000 100000 100000 100000 }
The non-atomic kernels lose updates when threads race on the same
element; the atomic kernels match the expected values exactly.
```
**Reading the numbers:**
- The non-atomic values vary from run to run — that nondeterminism is the race condition itself
- `max` is the sneakiest failure: 998070 looks plausible next to the correct 999990
- `cas_atomic` is orders of magnitude slower than `increment_atomic` for the same result: with ~100,000 threads contending per element, almost every CAS attempt fails and retries — use the dedicated intrinsic when one exists
- The `_std` and `_cuda` kernels are correct but slower than the intrinsics: both `cuda::std::atomic_ref` and `cuda::atomic_ref` default to sequentially-consistent ordering at system scope — a stronger guarantee than the relaxed device-scope intrinsics (which is why their timings match each other in every section above)
## Files
- `simpleAtomicIntrinsics.cu` — the twelve kernels and the host driver
- `README.md` — this file
- `CMakeLists.txt` — build configuration
## See Also
- [CUDA Programming Guide — Atomics](https://docs.nvidia.com/cuda/cuda-programming-guide/02-basics/writing-cuda-kernels.html#atomics)
- [CUDA Programming Guide — Atomic Functions](https://docs.nvidia.com/cuda/cuda-programming-guide/05-appendices/cpp-language-extensions.html#atomic-functions)
- [CUDA Core Compute Libraries — cuda::atomic](https://nvidia.github.io/cccl/unstable/libcudacxx/extended_api/synchronization_primitives/atomic.html)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
/* Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -25,110 +25,428 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* A simple program demonstrating trivial use of global memory atomic
* device functions (atomic*() functions).
/* A simple program demonstrating why atomic operations are needed, using the
* atomic intrinsics atomicAdd, atomicMax, and atomicCAS.
*
* 1,000,000 threads write into an array of only 10 integers (each thread maps
* to a slot with index % ARRAY_SIZE), so ~100,000 threads update every element
* at the same time. A plain update like g[i] = g[i] + 1 is three separate
* steps: read the value, modify it, write it back. When two threads interleave
* (both read the same old value, both write back the same result), one update
* silently overwrites the other and is lost. This is a race condition: the
* result depends on thread timing, not program logic, and changes every run.
*
* Each operation is therefore run four ways: a non-atomic kernel that loses
* most of its updates to these race conditions, and three atomic kernels that
* do the read-modify-write as one indivisible operation and give the exact
* result — first with the CUDA atomic intrinsics, then with
* cuda::std::atomic_ref from CCCL (the std::atomic API in device code), and
* finally with cuda::atomic_ref from CCCL (the CUDA-specific variant that
* natively supports CUDA thread scopes).
*/
// includes, system
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#define WINDOWS_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#endif
// Includes CUDA
#include <cuda_runtime.h>
// Utilities and timing functions
#include <helper_functions.h> // includes cuda.h and cuda_runtime_api.h
// Includes CCCL: std::atomic as usable in device code (cuda::std::atomic_ref)
#include <cuda/std/atomic>
// CUDA helper functions
#include <helper_cuda.h> // helper functions for CUDA error check
// cuda::atomic_ref<T>: like cuda::std::atomic_ref but CUDA-specific, supports thread scopes
#include <cuda/atomic>
// Includes, kernels
#include "simpleAtomicIntrinsics_kernel.cuh"
// and cuda::ceil_div for computing the launch grid size
#include <cuda/cmath>
const char *sampleName = "simpleAtomicIntrinsics";
////////////////////////////////////////////////////////////////////////////////
// Auto-Verification Code
bool testResult = true;
// Launch configuration: many threads hammering a small array
#define NUM_THREADS 1000000
#define BLOCK_WIDTH 1000
#define ARRAY_SIZE 10
////////////////////////////////////////////////////////////////////////////////
// Declaration, forward
void runTest(int argc, char **argv);
// Increment without atomics: threads race on shared elements and lose updates
__global__ void increment(int *g)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
// ceil_div rounds up the grid, this may launch extra threads
// Adding this guard to ignore extra threads
if (tid < NUM_THREADS) {
// each thread increments one element, wrapping at ARRAY_SIZE
int i = tid % ARRAY_SIZE;
g[i] = g[i] + 1;
}
}
extern "C" bool computeGold(int *gpuData, const int len);
// Increment with atomicAdd: read-modify-write is one indivisible operation
__global__ void increment_atomic(int *g)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < NUM_THREADS) {
int i = tid % ARRAY_SIZE;
atomicAdd(&g[i], 1);
}
}
// Increment with cuda::std::atomic_ref: wraps the plain int in g[] and exposes
// the exact std::atomic API (fetch_add, load, store, ...) inside device code
__global__ void increment_atomic_std(int *g)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < NUM_THREADS) {
int i = tid % ARRAY_SIZE;
cuda::std::atomic_ref<int> ref(g[i]);
ref.fetch_add(1);
}
}
// Increment with cuda::atomic_ref: like cuda::std::atomic_ref but CUDA-specific
// and natively supports CUDA thread scopes
__global__ void increment_atomic_cuda(int *g)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < NUM_THREADS) {
int i = tid % ARRAY_SIZE;
cuda::atomic_ref<int> ref(g[i]);
ref.fetch_add(1);
}
}
// Max without atomics: another thread can write between the compare and the
// store, so a smaller value can overwrite a larger one
__global__ void max(int *g)
{
// each thread contributes its global index as the value
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < NUM_THREADS) {
int i = tid % ARRAY_SIZE;
if (g[i] < tid)
g[i] = tid;
}
}
// Max with atomicMax: the compare and the store happen as one operation
__global__ void max_atomic(int *g)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < NUM_THREADS) {
int i = tid % ARRAY_SIZE;
atomicMax(&g[i], tid);
}
}
// Max with cuda::std::atomic_ref: std::atomic has no fetch_max, so max is
// built the standard C++ way — a compare_exchange loop that stops as soon as
// the stored value is already >= ours
__global__ void max_atomic_std(int *g)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < NUM_THREADS) {
int i = tid % ARRAY_SIZE;
cuda::std::atomic_ref<int> ref(g[i]);
int expected = ref.load();
while (expected < tid && !ref.compare_exchange_weak(expected, tid)) {
}
}
}
// Max with cuda::atomic_ref: same compare_exchange loop as the _std variant
__global__ void max_atomic_cuda(int *g)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < NUM_THREADS) {
int i = tid % ARRAY_SIZE;
cuda::atomic_ref<int> ref(g[i]);
int expected = ref.load();
while (expected < tid && !ref.compare_exchange_weak(expected, tid)) {
}
}
}
// Increment written as compare-and-swap, without atomics: the value can change
// between the compare and the swap, so increments are lost
__global__ void cas(int *g)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < NUM_THREADS) {
int i = tid % ARRAY_SIZE;
int expected = g[i]; // read
if (g[i] == expected) // compare
g[i] = expected + 1; // swap (not atomic with the compare!)
}
}
// Increment with an atomicCAS retry loop: the classic pattern for building
// any atomic operation out of compare-and-swap. atomicCAS returns the value
// it found: if another thread interfered, the swap did not happen and we retry
__global__ void cas_atomic(int *g)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < NUM_THREADS) {
int i = tid % ARRAY_SIZE;
int old = g[i];
int assumed;
do {
assumed = old;
old = atomicCAS(&g[i], assumed, assumed + 1);
} while (old != assumed);
}
}
// Increment with cuda::std::atomic_ref compare_exchange: the std::atomic way
// to write a CAS retry loop. On failure, expected is updated with the value
// actually found, so the loop just retries with fresh data
__global__ void cas_atomic_std(int *g)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < NUM_THREADS) {
int i = tid % ARRAY_SIZE;
cuda::std::atomic_ref<int> ref(g[i]);
int expected = ref.load();
while (!ref.compare_exchange_weak(expected, expected + 1)) {
}
}
}
// CAS increment with cuda::atomic_ref: same retry loop as the _std variant
__global__ void cas_atomic_cuda(int *g)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < NUM_THREADS) {
int i = tid % ARRAY_SIZE;
cuda::atomic_ref<int> ref(g[i]);
int expected = ref.load();
while (!ref.compare_exchange_weak(expected, expected + 1)) {
}
}
}
// Print every element of the array
void print_array(int *array, int size)
{
printf("{ ");
for (int i = 0; i < size; i++)
printf("%d ", array[i]);
printf("}\n");
}
////////////////////////////////////////////////////////////////////////////////
// Program main
////////////////////////////////////////////////////////////////////////////////
int main(int argc, char **argv)
{
printf("%s starting...\n", sampleName);
printf("=== Atomic vs. non-atomic operations (intrinsics, cuda::std::atomic_ref, cuda::atomic_ref) ===\n\n");
runTest(argc, argv);
// Select device 0 as the active GPU
int devID = 0;
cudaSetDevice(devID);
printf("%s completed, returned %s\n", sampleName, testResult ? "OK" : "ERROR!");
exit(testResult ? EXIT_SUCCESS : EXIT_FAILURE);
}
////////////////////////////////////////////////////////////////////////////////
//! Run a simple test for CUDA
////////////////////////////////////////////////////////////////////////////////
void runTest(int argc, char **argv)
{
cudaStream_t stream;
// This will pick the best possible CUDA capable device
findCudaDevice(argc, (const char **)argv);
StopWatchInterface *timer;
sdkCreateTimer(&timer);
sdkStartTimer(&timer);
unsigned int numThreads = 256;
unsigned int numBlocks = 64;
unsigned int numData = 11;
unsigned int memSize = sizeof(int) * numData;
// allocate mem for the result on host side
int *hOData;
checkCudaErrors(cudaMallocHost(&hOData, memSize));
// initialize the memory
for (unsigned int i = 0; i < numData; i++)
hOData[i] = 0;
// To make the AND and XOR tests generate something other than 0...
hOData[8] = hOData[10] = 0xff;
checkCudaErrors(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking));
// allocate device memory for result
int *dOData;
checkCudaErrors(cudaMalloc((void **)&dOData, memSize));
// copy host memory to device to initialize to zero
checkCudaErrors(cudaMemcpyAsync(dOData, hOData, memSize, cudaMemcpyHostToDevice, stream));
// execute the kernel
testKernel<<<numBlocks, numThreads, 0, stream>>>(dOData);
// Copy result from device to host
checkCudaErrors(cudaMemcpyAsync(hOData, dOData, memSize, cudaMemcpyDeviceToHost, stream));
checkCudaErrors(cudaStreamSynchronize(stream));
sdkStopTimer(&timer);
printf("Processing time: %f (ms)\n", sdkGetTimerValue(&timer));
sdkDeleteTimer(&timer);
// Compute reference solution
testResult = computeGold(hOData, numThreads * numBlocks);
// Cleanup memory
checkCudaErrors(cudaFreeHost(hOData));
checkCudaErrors(cudaFree(dOData));
// Query compute capability (major.minor) and number of SMs on the device
int major = 0, minor = 0, smCount = 0;
cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, devID);
cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, devID);
cudaDeviceGetAttribute(&smCount, cudaDevAttrMultiProcessorCount, devID);
// Print device info
printf("GPU Device %d: with compute capability %d.%d and Number of SMs %d\n\n", devID, major, minor, smCount);
// enough blocks to cover all threads, rounding up if not evenly divisible
int numBlocks = cuda::ceil_div(NUM_THREADS, BLOCK_WIDTH);
printf("%d total threads in %d blocks writing into %d array elements\n\n",
NUM_THREADS, numBlocks, ARRAY_SIZE);
// declare and allocate host memory
int h_array[ARRAY_SIZE];
const int ARRAY_BYTES = ARRAY_SIZE * sizeof(int);
// declare and allocate GPU memory (zeroed with cudaMemset before each run)
int *d_array;
cudaMalloc((void **)&d_array, ARRAY_BYTES);
// CUDA events record timestamps on the GPU stream to measure device time
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
float elapsed_ms;
// ----- add: every thread adds 1, each element should reach 100000 -----
printf("[add] expected: every element = %d\n", NUM_THREADS / ARRAY_SIZE);
cudaMemset((void *)d_array, 0, ARRAY_BYTES);
cudaEventRecord(start);
increment<<<numBlocks, BLOCK_WIDTH>>>(d_array);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsed_ms, start, stop);
cudaMemcpy(h_array, d_array, ARRAY_BYTES, cudaMemcpyDeviceToHost);
printf("%-36s (%g ms): ", "non-atomic", elapsed_ms);
print_array(h_array, ARRAY_SIZE);
cudaMemset((void *)d_array, 0, ARRAY_BYTES);
cudaEventRecord(start);
increment_atomic<<<numBlocks, BLOCK_WIDTH>>>(d_array);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsed_ms, start, stop);
cudaMemcpy(h_array, d_array, ARRAY_BYTES, cudaMemcpyDeviceToHost);
printf("%-36s (%g ms): ", "atomicAdd()", elapsed_ms);
print_array(h_array, ARRAY_SIZE);
cudaMemset((void *)d_array, 0, ARRAY_BYTES);
cudaEventRecord(start);
increment_atomic_std<<<numBlocks, BLOCK_WIDTH>>>(d_array);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsed_ms, start, stop);
cudaMemcpy(h_array, d_array, ARRAY_BYTES, cudaMemcpyDeviceToHost);
printf("%-36s (%g ms): ", "cuda::std::atomic_ref::fetch_add()", elapsed_ms);
print_array(h_array, ARRAY_SIZE);
cudaMemset((void *)d_array, 0, ARRAY_BYTES);
cudaEventRecord(start);
increment_atomic_cuda<<<numBlocks, BLOCK_WIDTH>>>(d_array);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsed_ms, start, stop);
cudaMemcpy(h_array, d_array, ARRAY_BYTES, cudaMemcpyDeviceToHost);
printf("%-36s (%g ms): ", "cuda::atomic_ref::fetch_add()", elapsed_ms);
print_array(h_array, ARRAY_SIZE);
// ----- max: every thread offers its index, element i should reach
// the largest index that maps to it: NUM_THREADS - ARRAY_SIZE + i -----
printf("\n[max] expected: element i = %d + i\n", NUM_THREADS - ARRAY_SIZE);
cudaMemset((void *)d_array, 0, ARRAY_BYTES);
cudaEventRecord(start);
max<<<numBlocks, BLOCK_WIDTH>>>(d_array);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsed_ms, start, stop);
cudaMemcpy(h_array, d_array, ARRAY_BYTES, cudaMemcpyDeviceToHost);
printf("%-46s (%g ms): ", "non-atomic", elapsed_ms);
print_array(h_array, ARRAY_SIZE);
cudaMemset((void *)d_array, 0, ARRAY_BYTES);
cudaEventRecord(start);
max_atomic<<<numBlocks, BLOCK_WIDTH>>>(d_array);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsed_ms, start, stop);
cudaMemcpy(h_array, d_array, ARRAY_BYTES, cudaMemcpyDeviceToHost);
printf("%-46s (%g ms): ", "atomicMax()", elapsed_ms);
print_array(h_array, ARRAY_SIZE);
cudaMemset((void *)d_array, 0, ARRAY_BYTES);
cudaEventRecord(start);
max_atomic_std<<<numBlocks, BLOCK_WIDTH>>>(d_array);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsed_ms, start, stop);
cudaMemcpy(h_array, d_array, ARRAY_BYTES, cudaMemcpyDeviceToHost);
printf("%-46s (%g ms): ", "cuda::std::atomic_ref::compare_exchange_weak()", elapsed_ms);
print_array(h_array, ARRAY_SIZE);
cudaMemset((void *)d_array, 0, ARRAY_BYTES);
cudaEventRecord(start);
max_atomic_cuda<<<numBlocks, BLOCK_WIDTH>>>(d_array);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsed_ms, start, stop);
cudaMemcpy(h_array, d_array, ARRAY_BYTES, cudaMemcpyDeviceToHost);
printf("%-46s (%g ms): ", "cuda::atomic_ref::compare_exchange_weak()", elapsed_ms);
print_array(h_array, ARRAY_SIZE);
// ----- CAS: increment built from compare-and-swap, same expected
// result as add -----
printf("\n[CAS] expected: every element = %d\n", NUM_THREADS / ARRAY_SIZE);
cudaMemset((void *)d_array, 0, ARRAY_BYTES);
cudaEventRecord(start);
cas<<<numBlocks, BLOCK_WIDTH>>>(d_array);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsed_ms, start, stop);
cudaMemcpy(h_array, d_array, ARRAY_BYTES, cudaMemcpyDeviceToHost);
printf("%-46s (%g ms): ", "non-atomic", elapsed_ms);
print_array(h_array, ARRAY_SIZE);
cudaMemset((void *)d_array, 0, ARRAY_BYTES);
cudaEventRecord(start);
cas_atomic<<<numBlocks, BLOCK_WIDTH>>>(d_array);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsed_ms, start, stop);
cudaMemcpy(h_array, d_array, ARRAY_BYTES, cudaMemcpyDeviceToHost);
printf("%-46s (%g ms): ", "atomicCAS()", elapsed_ms);
print_array(h_array, ARRAY_SIZE);
cudaMemset((void *)d_array, 0, ARRAY_BYTES);
cudaEventRecord(start);
cas_atomic_std<<<numBlocks, BLOCK_WIDTH>>>(d_array);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsed_ms, start, stop);
cudaMemcpy(h_array, d_array, ARRAY_BYTES, cudaMemcpyDeviceToHost);
printf("%-46s (%g ms): ", "cuda::std::atomic_ref::compare_exchange_weak()", elapsed_ms);
print_array(h_array, ARRAY_SIZE);
cudaMemset((void *)d_array, 0, ARRAY_BYTES);
cudaEventRecord(start);
cas_atomic_cuda<<<numBlocks, BLOCK_WIDTH>>>(d_array);
cudaEventRecord(stop);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsed_ms, start, stop);
cudaMemcpy(h_array, d_array, ARRAY_BYTES, cudaMemcpyDeviceToHost);
printf("%-46s (%g ms): ", "cuda::atomic_ref::compare_exchange_weak()", elapsed_ms);
print_array(h_array, ARRAY_SIZE);
printf("\nThe non-atomic kernels lose updates when threads race on the same\n");
printf("element; the atomic kernels match the expected values exactly.\n");
// free GPU memory allocation and timing events, then exit
cudaEventDestroy(start);
cudaEventDestroy(stop);
cudaFree(d_array);
return 0;
}

View File

@@ -1,183 +0,0 @@
/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of NVIDIA CORPORATION nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <math.h>
#include <stdio.h>
#define min(a, b) (a) < (b) ? (a) : (b)
#define max(a, b) (a) > (b) ? (a) : (b)
////////////////////////////////////////////////////////////////////////////////
// export C interface
extern "C" int computeGold(int *gpuData, const int len);
////////////////////////////////////////////////////////////////////////////////
//! Compute reference data set
//! Each element is multiplied with the number of threads / array length
//! @param reference reference data, computed but preallocated
//! @param idata input data as provided to device
//! @param len number of elements in reference / idata
////////////////////////////////////////////////////////////////////////////////
int computeGold(int *gpuData, const int len)
{
int val = 0;
for (int i = 0; i < len; ++i) {
val += 10;
}
if (val != gpuData[0]) {
printf("atomicAdd failed\n");
return false;
}
val = 0;
for (int i = 0; i < len; ++i) {
val -= 10;
}
if (val != gpuData[1]) {
printf("atomicSub failed\n");
return false;
}
bool found = false;
for (int i = 0; i < len; ++i) {
// third element should be a member of [0, len)
if (i == gpuData[2]) {
found = true;
break;
}
}
if (!found) {
printf("atomicExch failed\n");
return false;
}
val = -(1 << 8);
for (int i = 0; i < len; ++i) {
// fourth element should be len-1
val = max(val, i);
}
if (val != gpuData[3]) {
printf("atomicMax failed\n");
return false;
}
val = 1 << 8;
for (int i = 0; i < len; ++i) {
val = min(val, i);
}
if (val != gpuData[4]) {
printf("atomicMin failed\n");
return false;
}
int limit = 17;
val = 0;
for (int i = 0; i < len; ++i) {
val = (val >= limit) ? 0 : val + 1;
}
if (val != gpuData[5]) {
printf("atomicInc failed\n");
return false;
}
limit = 137;
val = 0;
for (int i = 0; i < len; ++i) {
val = ((val == 0) || (val > limit)) ? limit : val - 1;
}
if (val != gpuData[6]) {
printf("atomicDec failed\n");
return false;
}
found = false;
for (int i = 0; i < len; ++i) {
// eighth element should be a member of [0, len)
if (i == gpuData[7]) {
found = true;
break;
}
}
if (!found) {
printf("atomicCAS failed\n");
return false;
}
val = 0xff;
for (int i = 0; i < len; ++i) {
// 9th element should be 1
val &= (2 * i + 7);
}
if (val != gpuData[8]) {
printf("atomicAnd failed\n");
return false;
}
val = 0;
for (int i = 0; i < len; ++i) {
// 10th element should be 0xff
val |= (1 << i);
}
if (val != gpuData[9]) {
printf("atomicOr failed\n");
return false;
}
val = 0xff;
for (int i = 0; i < len; ++i) {
// 11th element should be 0xff
val ^= i;
}
if (val != gpuData[10]) {
printf("atomicXor failed\n");
return false;
}
return true;
}

View File

@@ -1,83 +0,0 @@
/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of NVIDIA CORPORATION nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* Simple kernel demonstrating atomic functions in device code. */
#ifndef _SIMPLEATOMICS_KERNEL_H_
#define _SIMPLEATOMICS_KERNEL_H_
////////////////////////////////////////////////////////////////////////////////
//! Simple test kernel for atomic instructions
//! @param g_idata input data in global memory
//! @param g_odata output data in global memory
////////////////////////////////////////////////////////////////////////////////
__global__ void testKernel(int *g_odata)
{
// access thread id
const unsigned int tid = blockDim.x * blockIdx.x + threadIdx.x;
// Test various atomic instructions
// Arithmetic atomic instructions
// Atomic addition
atomicAdd(&g_odata[0], 10);
// Atomic subtraction (final should be 0)
atomicSub(&g_odata[1], 10);
// Atomic exchange
atomicExch(&g_odata[2], tid);
// Atomic maximum
atomicMax(&g_odata[3], tid);
// Atomic minimum
atomicMin(&g_odata[4], tid);
// Atomic increment (modulo 17+1)
atomicInc((unsigned int *)&g_odata[5], 17);
// Atomic decrement
atomicDec((unsigned int *)&g_odata[6], 137);
// Atomic compare-and-swap
atomicCAS(&g_odata[7], tid - 1, tid);
// Bitwise atomic instructions
// Atomic AND
atomicAnd(&g_odata[8], 2 * tid + 7);
// Atomic OR
atomicOr(&g_odata[9], 1 << tid);
// Atomic XOR
atomicXor(&g_odata[10], tid);
}
#endif // #ifndef _SIMPLEATOMICS_KERNEL_H_