mirror of
https://github.com/NVIDIA/cuda-samples.git
synced 2026-10-13 00:08:26 +08:00
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:
@@ -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
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"nvidia.nsight-vscode-edition",
|
||||
"ms-vscode.cpptools",
|
||||
"ms-vscode.makefile-tools"
|
||||
]
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
Reference in New Issue
Block a user