假设您有一个想要运行2048次的cuda内核,那么您可以像这样定义内核:
__global__ void run2048Times(){ }
然后从主代码中调用它:
run2048Times<<<2,1024>>>();
到目前为止一切似乎都很顺利。但是现在说,当您调用内核数百万次时,为了进行调试,您需要验证您实际多次调用内核。
我所做的是每次内核运行时都将指针传递给内核,然后将指针传递给++。
__global__ void run2048Times(int *kernelCount){
kernelCount[0]++; // Add to the pointer
}
然而,当我将指针复制回主函数时,我得到“2”。
起初它让我感到困惑,然后经过5分钟的咖啡和来回踱步我意识到这可能是有道理的,因为cuda内核同时运行了1024个自身实例,这意味着内核会覆盖“kernelCount” [0]“而不是真正添加它。
所以我决定这样做:
__global__ void run2048Times(int *kernelCount){
// Get the id of the kernel
int id = blockIdx.x * blockDim.x + threadIdx.x;
// If the id is bigger than the pointer overwrite it
if(id > kernelCount[0]){
kernelCount[0] = id;
}
}
天才!!我保证这可以保证工作。直到我运行它并获得0到2000之间的各种数字。
这告诉我上面提到的问题仍然在这里发生。
有没有办法做到这一点,即使它涉及强制内核暂停并等待彼此运行?
答案 0 :(得分:1)
该计数器的唯一要点就是进行分析(即分析代码的运行方式),而不是实际计算某些东西(即对程序没有任何功能性好处)。
有可用于此任务的分析工具。例如,nvprof给出了代码库中每个内核的调用次数和一些时间度量标准。
答案 1 :(得分:1)
假设这是一个简化的示例,并且您实际上并不像其他人已经建议的那样尝试进行性能分析,但是想要在更复杂的场景中使用它,您可以使用atomicAdd
获得所需的结果,这将确保增量操作作为单个原子操作执行:
__global__ void run2048Times(int *kernelCount){
atomicAdd(kernelCount, 1); // Add to the pointer
}
您的第一个解决方案的问题是它被编译成以下PTX代码(有关PTX指令的说明,请参阅here):
ld.global.u32 %r1, [%rd2];
add.s32 %r2, %r1, 1;
st.global.u32 [%rd2], %r2;
您可以通过使用nvcc
选项调用--ptx
来验证这一点,以便仅生成中间代表。
这里可能发生的是以下时间轴,假设您启动了2个线程(注意:这是一个简化的示例,而不是GPU究竟是如何工作的,但它足以说明问题):< / p>
0
从0
kernelCount
1
从0
kernelCount
0
通过1
0
将1
存储回kernelCount
1
通过1
1
将1
存储回kernelCount
,即使启动了2个帖子,您也会得到1
。
即使线程是按顺序启动的,第二个解决方案也是错误的,因为线程索引是从0开始的。所以我假设你想要这样做:
__global__ void run2048Times(int *kernelCount){
// Get the id of the kernel
int id = blockIdx.x * blockDim.x + threadIdx.x;
// If the id is bigger than the pointer overwrite it
if(id + 1 > kernelCount[0]){
kernelCount[0] = id + 1;
}
}
这将编译成:
ld.global.u32 %r5, [%rd1];
setp.lt.s32 %p1, %r1, %r5;
@%p1 bra BB0_2;
add.s32 %r6, %r1, 1;
st.global.u32 [%rd1], %r6;
BB0_2:
ret;
这里可能发生的是以下时间表:
0
从0
kernelCount
1
从0
kernelCount
1
将0
与1 + 1
进行比较,并将2
存储到kernelCount
0
将0
与0 + 1
进行比较,并将1
存储到kernelCount
你最终得错了1的结果。
如果你想更好地理解同步和非原子操作的问题,我建议你选择一本好的并行编程/ CUDA书。
修改:
为完整起见,使用atomicAdd
的版本编译为:
atom.global.add.u32 %r1, [%rd2], 1;