我正在阅读这本书"Cuda by Example" by Jason Sanders and Edward Kandrot并且有一个关于他们使用锁来计算两个数组的点积的问题(见链接pdf的第254-258页)。他们定义了他们的lock.h头文件:
#ifndef __LOCK_H__
#define __LOCK_H__
struct Lock{
int *mutex;
Lock(){
int state = 0;
cudaMalloc((void**)&mutex, sizeof(int));
cudaMemcpy(mutex, &state, sizeof(int), cudaMemcpyHostToDevice);
}
~Lock(){
cudaFree(mutex);
}
__device__ void lock(){
while (atomicCAS(mutex, 0 ,1) != 0);
}
__device__ void unlock(){
atomicExch(mutex, 0);
}
};
#endif
然后他们将他们的点产品内核称为:
int main()
{
// bunch of code, initialization etc
Lock lock;
dot<<<blocksPerGrid,threadsPerBlock>>>(lock, dev_a, dev_b, dev_c);
// more code, frees, etc
}
点内核声明为:
__global__ void dot(Lock lock, float *a, float *b, float *c);
这是否会创建无效的free,因为Lock结构不包含复制构造函数?我们通过值传递锁定,它只是按值复制互斥锁指针。当我们退出内核时,析构函数在这个互斥锁指针上调用cudaFree。当我们退出main函数时,然后我再次调用析构函数,但是现在mutex已经被释放了!我问,因为在一个不同的更大的代码中,我使用同样的想法时得到cudeErrorInvalidDevicePointer错误,我认为问题是没有复制构造函数。
答案 0 :(得分:1)
是的,你是对的,还会打一个额外的电话 - 你也可以自己查一下......
以下是验证的简单示例:
#include <iostream>
#include <cuda_runtime.h>
struct A {
A() { std::cout << "ctor for " << this << std::endl << std::flush; }
~A() { std::cout << "dtor for " << this << std::endl << std::flush; }
};
__global__ void foo(A device_a) { }
int main(void) {
A host_a;
foo<<<1,1>>>(host_a);
cudaDeviceReset();
return 0;
}
输出是:
ctor for 0x7ffe584c85bf
dtor for 0x7ffe584c85df
dtor for 0x7ffe584c85bf
所以,你得到了默认的拷贝ctor;并且两个副本的构造和销毁发生在主机上(这些是仅限主机的功能,而设备没有<iostream>
)。我实际上发现有点奇怪,但我想这是CUDA的“C-ish”起源的一部分,处理像POD这样的内核参数。看到ctors和dtors如何获得__device__
和__host__
限定符,这一点尤为令人惊讶。
但是,在该示例中额外调用cudaFree()
应该不是问题(手指交叉):CUDA Runtime API手册第3.9节说调用应该失败并返回cudaErrorInvalidDevicePointer
。
尽管如此,这种糟糕的编程习惯和结构应该以不同的方式编写IMO。