是否有适当的 CUDA atomicLoad 函数?
Is there proper CUDA atomicLoad function?
我遇到过 CUDA atomic API 没有 atomicLoad 函数的问题。
在 Whosebug 上搜索后,我发现了以下 CUDA 实现 atomicLoad
但看起来这个函数在下面的例子中不起作用:
#include <cassert>
#include <iostream>
#include <cuda_runtime_api.h>
template <typename T>
__device__ T atomicLoad(const T* addr) {
const volatile T* vaddr = addr; // To bypass cache
__threadfence(); // for seq_cst loads. Remove for acquire semantics.
const T value = *vaddr;
// fence to ensure that dependent reads are correctly ordered
__threadfence();
return value;
}
__global__ void initAtomic(unsigned& count, const unsigned initValue) {
count = initValue;
}
__global__ void addVerify(unsigned& count, const unsigned biasAtomicValue) {
atomicAdd(&count, 1);
// NOTE: When uncomment the following while loop the addVerify is stuck,
// it cannot read last proper value in variable count
// while (atomicLoad(&count) != (1024 * 1024 + biasAtomicValue)) {
// printf("count = %u\n", atomicLoad(&count));
// }
}
int main() {
std::cout << "Hello, CUDA atomics!" << std::endl;
const auto atomicSize = sizeof(unsigned);
unsigned* datomic = nullptr;
cudaMalloc(&datomic, atomicSize);
cudaStream_t stream;
cudaStreamCreate(&stream);
constexpr unsigned biasAtomicValue = 11;
initAtomic<<<1, 1, 0, stream>>>(*datomic, biasAtomicValue);
addVerify<<<1024, 1024, 0, stream>>>(*datomic, biasAtomicValue);
cudaStreamSynchronize(stream);
unsigned countHost = 0;
cudaMemcpyAsync(&countHost, datomic, atomicSize, cudaMemcpyDeviceToHost, stream);
assert(countHost == 1024 * 1024 + biasAtomicValue);
cudaStreamDestroy(stream);
return 0;
}
如果您取消注释带有 atomicLoad 的部分,那么应用程序将卡住...
也许我错过了什么?有没有正确的方法来加载原子修改的变量?
P.S.: 我知道存在 cuda::atomic
实现,但是我的硬件
不支持这个 API
由于 warp 以锁步方式工作(至少在旧 arch 中),如果您在同一个 warp 中对一个线程和生产者进行条件等待,那么 warp 可能会卡在等待中如果它 starts/is 先执行。也许只有具有异步 warp 线程调度的最新架构才能做到这一点。例如,您应该在 运行 之前查询 minor-major 版本的 cuda 体系结构。 Volta及以后的都可以。
您还启动了 100 万个线程并同时等待所有线程。 GPU 可能没有那么多执行 ports/pipeline 可用性来拥有 100 万个线程 in-flight。也许它只适用于 64k CUDA 管道的 GPU(假设每个管道有 16 个线程在运行)。无需等待数百万个线程,只需在条件发生时从主内核中生成 sub-kernels。动态并行是关键特性。您还应该检查最低 minor-major cuda 版本以使用动态并行性,以防有人使用古老的 nvidia 卡。
Atomic-add命令returns目标地址中的旧值。如果您打算仅在条件之后调用第三个内核一次,那么您可以在开始动态并行性之前通过“if”简单地检查返回值。
您正在打印 100 万次,这对性能不利,如果您的速度较慢,可能需要一些时间才能在控制台输出中显示文本 CPU/RAM。
最后,您可以通过 运行 首先在共享内存上优化原子操作的性能,然后每个块只进行一次全局原子操作。如果线程数多于条件值(假设总是 1 个增量值),这将错过条件点,因此它可能不适用于所有算法。
我遇到过 CUDA atomic API 没有 atomicLoad 函数的问题。 在 Whosebug 上搜索后,我发现了以下 CUDA 实现 atomicLoad
但看起来这个函数在下面的例子中不起作用:
#include <cassert>
#include <iostream>
#include <cuda_runtime_api.h>
template <typename T>
__device__ T atomicLoad(const T* addr) {
const volatile T* vaddr = addr; // To bypass cache
__threadfence(); // for seq_cst loads. Remove for acquire semantics.
const T value = *vaddr;
// fence to ensure that dependent reads are correctly ordered
__threadfence();
return value;
}
__global__ void initAtomic(unsigned& count, const unsigned initValue) {
count = initValue;
}
__global__ void addVerify(unsigned& count, const unsigned biasAtomicValue) {
atomicAdd(&count, 1);
// NOTE: When uncomment the following while loop the addVerify is stuck,
// it cannot read last proper value in variable count
// while (atomicLoad(&count) != (1024 * 1024 + biasAtomicValue)) {
// printf("count = %u\n", atomicLoad(&count));
// }
}
int main() {
std::cout << "Hello, CUDA atomics!" << std::endl;
const auto atomicSize = sizeof(unsigned);
unsigned* datomic = nullptr;
cudaMalloc(&datomic, atomicSize);
cudaStream_t stream;
cudaStreamCreate(&stream);
constexpr unsigned biasAtomicValue = 11;
initAtomic<<<1, 1, 0, stream>>>(*datomic, biasAtomicValue);
addVerify<<<1024, 1024, 0, stream>>>(*datomic, biasAtomicValue);
cudaStreamSynchronize(stream);
unsigned countHost = 0;
cudaMemcpyAsync(&countHost, datomic, atomicSize, cudaMemcpyDeviceToHost, stream);
assert(countHost == 1024 * 1024 + biasAtomicValue);
cudaStreamDestroy(stream);
return 0;
}
如果您取消注释带有 atomicLoad 的部分,那么应用程序将卡住...
也许我错过了什么?有没有正确的方法来加载原子修改的变量?
P.S.: 我知道存在 cuda::atomic
实现,但是我的硬件
由于 warp 以锁步方式工作(至少在旧 arch 中),如果您在同一个 warp 中对一个线程和生产者进行条件等待,那么 warp 可能会卡在等待中如果它 starts/is 先执行。也许只有具有异步 warp 线程调度的最新架构才能做到这一点。例如,您应该在 运行 之前查询 minor-major 版本的 cuda 体系结构。 Volta及以后的都可以。
您还启动了 100 万个线程并同时等待所有线程。 GPU 可能没有那么多执行 ports/pipeline 可用性来拥有 100 万个线程 in-flight。也许它只适用于 64k CUDA 管道的 GPU(假设每个管道有 16 个线程在运行)。无需等待数百万个线程,只需在条件发生时从主内核中生成 sub-kernels。动态并行是关键特性。您还应该检查最低 minor-major cuda 版本以使用动态并行性,以防有人使用古老的 nvidia 卡。
Atomic-add命令returns目标地址中的旧值。如果您打算仅在条件之后调用第三个内核一次,那么您可以在开始动态并行性之前通过“if”简单地检查返回值。
您正在打印 100 万次,这对性能不利,如果您的速度较慢,可能需要一些时间才能在控制台输出中显示文本 CPU/RAM。
最后,您可以通过 运行 首先在共享内存上优化原子操作的性能,然后每个块只进行一次全局原子操作。如果线程数多于条件值(假设总是 1 个增量值),这将错过条件点,因此它可能不适用于所有算法。