__global__函数如何能像C / C ++那样返回一个值或BREAK

时间:2012-09-20 03:35:09

标签: cuda

最近我一直在做CUDA上的字符串比较工作,我想知道__global__函数在找到我正在寻找的确切字符串时如何返回一个值。

我的意思是,我需要__global__函数,它包含大量的线程来同时在一个大的字符串池中查找某个字符串,我希望一旦捕获了完整的字符串,__ global__函数就可以停止所有线程并返回主函数,并告诉我“他做了”!

我正在使用CUDA C.我怎么可能实现这个目标?

3 个答案:

答案 0 :(得分:19)

CUDA(或NVIDIA GPU)无法让一个线程中断所有正在运行的线程的执行。一旦找到结果就不能立即退出内核,今天就不可能了。

但是,在一个线程找到结果后,您可以让所有线程尽快退出 。这是一个如何做到这一点的模型。

__global___ void kernel(volatile bool *found, ...) 
{
    while (!(*found) && workLeftToDo()) {

       bool iFoundIt = do_some_work(...); // see notes below

       if (iFoundIt) *found = true;
    }
}

关于此的一些注释。

  1. 请注意volatile的使用。这个很重要。
  2. 确保在启动内核之前初始化found - 必须是设备指针 - false
  3. 当另一个线程更新found时,线程不会立即退出。它们只会在下次返回while循环的顶部时退出。
  4. 如何实施do_some_work事项。如果工作太多(或者变量太大),那么在找到结果后退出的延迟将是长的(或可变的)。如果工作太少,那么你的线程将花费大部分时间来检查found,而不是做有用的工作。
  5. do_some_work还负责分配任务(即计算/递增索引),以及如何执行此操作是特定于问题的。
  6. 如果您启动的块数远远大于当前GPU上内核的最大占用率,并且在第一个运行的“wave”线程块中找不到匹配项,则此内核(以及下一个内核) )可以僵局。如果在第一个波形中找到匹配,则后面的块将仅在found == true之后运行,这意味着它们将启动,然后立即退出。解决方案是仅启动可以同时驻留的块(也称为“最大启动”),并相应地更新任务分配。
  7. 如果任务数量相对较少,您可以将while替换为if并运行足够的线程来覆盖任务数量。然后就没有死锁的机会(但前一点的第一部分适用)。
  8. workLeftToDo()是特定于问题的,但是当没有工作要做时它会返回false,因此在找不到匹配项的情况下我们不会死锁。
  9. 现在,上面的内容可能导致过多的分区驻留(所有线程都在同一个内存上),特别是在没有L1缓存的旧架构上。因此,您可能希望编写一个稍微复杂的版本,使用每个块的共享状态。

    __global___ void kernel(volatile bool *found, ...) 
    {
        volatile __shared__ bool someoneFoundIt;
    
        // initialize shared status
        if (threadIdx.x == 0) someoneFoundIt = *found;
        __syncthreads();
    
        while(!someoneFoundIt && workLeftToDo()) {
    
           bool iFoundIt = do_some_work(...); 
    
           // if I found it, tell everyone they can exit
           if (iFoundIt) { someoneFoundIt = true; *found = true; }
    
           // if someone in another block found it, tell 
           // everyone in my block they can exit
           if (threadIdx.x == 0 && *found) someoneFoundIt = true;
    
           __syncthreads();
        }
    }
    

    这样,每个块一个线程轮询全局变量,并且只有找到匹配的线程才会写入,因此全局内存流量最小化。

    除此之外:__ global__函数是无效的,因为很难定义如何将1000个线程中的值返回到单个CPU线程中。用户设计适合其目的的设备或零拷贝存储器中的返回数组是微不足道的,但很难建立通用机制。

    免责声明:使用浏览器编写的代码,未经测试,未经验证。

答案 1 :(得分:5)

如果您有冒险精神,停止内核执行的另一种方法就是执行

// (write result to memory here)
__threadfence();
asm("trap;");

如果找到答案。

这不需要轮询内存,但不如Mark Harris建议的解决方案,因为它使内核以错误条件退出。这可能会掩盖实际错误(因此请务必以明确允许从错误中成功执行的方式写出结果),并且可能会导致其他打嗝或降低整体性能,因为驱动程序会将此视为异常。 / p>

如果您寻求安全而简单的解决方案,请转而选择马克哈里斯的建议。

答案 2 :(得分:0)

全局函数并不像你认为的那样包含大量的线程。它只是一个在设备上运行的内核函数,通过传递指定线程模型的参数来调用。 CUDA采用的模型是2D网格模型,然后是网格中每个块内部的3D线程模型。

对于你遇到的问题类型,除了在每个块中使用1D线程的1D网格之外,没有必要使用任何东西,因为字符串池与其他问题一样分裂成2D是没有意义的(例如矩阵)乘法)

我将通过一个简单的例子说明字符串池中的100个字符串,并且您希望以并行方式而不是按顺序检查所有字符串。

//main
//Should cudamalloc and cudacopy to device up before this code
dim3 dimGrid(10, 1); // 1D grid with 10 blocks
dim3 dimBlocks(10, 1); //1D Blocks with 10 threads 
fun<<<dimGrid, dimBlocks>>>(, Height)
//cudaMemCpy answerIdx back to integer on host

//kernel (Not positive on these types as my CUDA is very rusty
__global__ void fun(char *strings[], char *stringToMatch, int *answerIdx)
{
    int idx = blockIdx.x * 10 + threadIdx.x;

    //Obviously use whatever function you've been using for string comparison
    //I'm just using == for example's sake
    if(strings[idx] == stringToMatch)
    { 
       *answerIdx = idx
    }
} 

这显然不是最有效的,并且很可能不是通过参数并使用内存和CUDA的确切方法,但我希望它能够分解工作负载以及“全局”函数执行在许多不同的核心,所以你不能真正告诉他们所有人停下来。可能有一种我不熟悉的方法,但只需将工作负载分配到设备上(当然是以明智的方式),您将获得的速度提升将为您带来巨大的性能提升。为了了解线程模型,我强烈建议您阅读Nvidia网站上的CUDA文档。他们将极大地帮助您,并教您设置网格和阻止最佳性能的最佳方法。