在ROS上下文中使用基于CUDA的函数的正确方法

时间:2018-05-02 04:06:59

标签: c++ cuda ros

我正在研究基于ROS的管道,其主要功能是订阅图像主题并连续执行特征检测,匹配等功能。为了使这个管道更快,我试图使用基于CUDA的检测和匹配模块作为我的包的一部分。在这个问题的上下文中,我假设一个简单的管道,我订阅了一个图像主题,并且在每次图像可用时调用的订阅者回调中,调用了两个不同类的成员函数:一个用于检测,另一个用于检测匹配,每个都包含自己的CUDA内核。有点类似于在循环中执行这两个函数。

第一个函数获取图像数据并计算特征关键点和描述符,并将它们返回给主机。然后我将这些描述符复制回GPU内存,我需要将它们与另一组属于参考图像的描述符进行匹配。

例如,这就是独立匹配函数的样子:

// Detection module returns a struct featureData, containing keypoints and descriptors 
// in featureData.kps and featureData.desc

uint64_t* d_desc;
cudaMalloc(&d_desc, 64 * featureData.kps.size());
cudaMemcpy(d_desc, &featureData., 64 * (featureData.kps.size()), cudaMemcpyHostToDevice);

cudaDeviceSetCacheConfig(cudaFuncCachePreferL1);
cudaDeviceSetSharedMemConfig(cudaSharedMemBankSizeEightByte);

// Create texture object for descriptors

struct cudaResourceDesc resDesc;
memset(&resDesc, 0, sizeof(resDesc));
resDesc.resType = cudaResourceTypeLinear;
resDesc.res.linear.devPtr = d_desc;
resDesc.res.linear.desc.f = cudaChannelFormatKindUnsigned;
resDesc.res.linear.desc.x = 32;
resDesc.res.linear.desc.y = 32;
resDesc.res.linear.sizeInBytes = 64 * featureData.kps.size();

struct cudaTextureDesc texDesc;
memset(&texDesc, 0, sizeof(texDesc));
texDesc.addressMode[0] = cudaAddressModeBorder;
texDesc.addressMode[1] = cudaAddressModeBorder;
texDesc.filterMode = cudaFilterModePoint;
texDesc.readMode = cudaReadModeElementType;
texDesc.normalizedCoords = 0;
cudaTextureObject_t tex_q = 0;
cudaCreateTextureObject(&tex_q, &resDesc, &texDesc, nullptr);

// Allocate space for match results
int* d_matches;
cudaMalloc(&d_matches, 4 * featureData.kps.size());

// Launch the matching kernel
CUDAmatch(d_descRef, static_cast<int>(refData.kps.size()), tex_q, static_cast<int>(featureData.kps.size()), d_matches, threshold);

// d_descRef is memory pointed to by a uint64_t* for the reference descriptors.

在这种情况下,我有几个问题,因为这是我第一次涉足基于GPU的开发。

  1. 当需要执行匹配时,将描述符等复制到设备存储器中,然后将结果复制回来。我是否应该在每次执行匹配后释放设备内存指针并在下一次回调中重新分配(cudaMalloc())?描述符的长度将根据检测到的特征数量而变化。或者是否有更有效的方法只分配一次内存并重用它?
  2. 检测和匹配功能还使用cudaResourceDesccudaTextureDesc等对象,这些对象在每次执行结束时都会超出范围,因此应该被销毁。我应该以任何其他特定方式处理它们吗?
  3. 我认为在执行这两个函数之后我需要cudaDeviceSynchronize()。我是对的吗?
  4. 我可以安全地离开&#34;参考&#34; GPU内存中的描述符,只在我需要时更新它们?

1 个答案:

答案 0 :(得分:2)

  

我是否应该在每次执行匹配后释放这些设备内存指针并在下一次回调中重新分配(cudaMalloc())?

可能不是。这似乎是不必要和耗时的。

  

或者是否有更有效的方法只分配一次内存并重复使用它?

可能。例如,您可以确定可能需要的最大大小,为此分配,然后将指针传递给事件处理循环,并重用分配。

  

检测和匹配功能还使用了cudaResourceDesc和cudaTextureDesc等对象,这些对象在每次执行结束时都会超出范围,因此应该被销毁。我应该以任何其他特定方式处理它们吗?

同样,您可以在更高的范围内创建它们,并将对它们的引用传递到事件处理系统中。但是,我认为消费者在这里的主要时间将是填充纹理的数据副本,以及纹理的绑定。无论如何都必须重复这些(大概)。但是如果你有纹理对象的后备存储的底层分配,那么你可能不需要为此重新分配,请参阅之前的注释。

  

我假设在执行这两个函数之后我需要cudaDeviceSynchronize()。我是对的吗?

对我而言,这是不必要的。您还没有展示完整的示例,但是如果在功能结束时,从设备到主机的数据副本,则可能就足够了。 cudaMemcpy是阻止功能。如果您在物理/逻辑上统一的内存情况下运行,例如TX1 / TX2,则可能需要一个同步点,以确保数据在您在主机代码中使用之前有效。

  

我可以安全地离开&#34;参考&#34; GPU内存中的描述符,只在我需要时更新它们?

我不知道为什么不。由cudaMalloc创建的分配不会超出范围&#34;直到申请终止,或明确免费cudaFree。如果您将数据复制到这样的分配,它应该在应用程序的持续时间内保持不变,除非您以某种方式覆盖它(或释放基础分配)。