【问题标题】:Why not cudaMemcpyAsync(host to device) and CUDA kernel are parallel?为什么 cudaMemcpyAsync(主机到设备)和 CUDA 内核不是并行的?
【发布时间】:2015-07-16 21:59:39
【问题描述】:

我已加载大小为1080 x 1920 的图像(8 位,无符号字符)。出于测试目的,我使用for loop 处理同一图像 4 次,然后生成其时间线分析。

策略:我将图像分为 3 部分。我制作了三个流来处理整个图像。

我在下面提供了一个最小的工作示例。很抱歉,它需要使用 OpenCV 的图像,但我不知道如何在不使用 OpenCV 加载图像的情况下模拟相同的情况。

问题:时间线分析显示第一个流已完成数据传输,但分配给它的内核仍未启动。分配给第一个流的内核和第三个流的数据传输是并行的。 那么,我的问题是为什么第一个流的内核处理没有与第二个流的数据传输并行开始?

GPU: NVIDIA Quadro K2000,兼容 3.0

时间线配置文件:每个流都被分配了不同的颜色。

我的代码:

__global__ void multiStream_ColorTransformation_kernel(int numChannels, int iw, int ih, unsigned char *ptr_source, unsigned char *ptr_dst)
{
    // Calculate our pixel's location
    int x = (blockIdx.x * blockDim.x) + threadIdx.x;
    int y = (blockIdx.y * blockDim.y) + threadIdx.y;

    // Operate only if we are in the correct boundaries
    if (x >= 0 && x < iw && y >= 0 && y < ih / 3)
    {
        ptr_dst[numChannels*  (iw*y + x) + 0] = ptr_source[numChannels*  (iw*y + x) + 0];
        ptr_dst[numChannels*  (iw*y + x) + 1] = ptr_source[numChannels*  (iw*y + x) + 1];
        ptr_dst[numChannels*  (iw*y + x) + 2] = ptr_source[numChannels*  (iw*y + x) + 2];

    }
}

void callMultiStreamingCudaKernel(unsigned char *dev_src, unsigned char *dev_dst, int numChannels, int iw, int ih, cudaStream_t *ptr_stream)
{

    dim3 numOfBlocks((iw / 20), (ih / 20)); //DON'T multiply by 3 because we have 1/3 data of image
    dim3 numOfThreadsPerBlocks(20, 20);
    multiStream_ColorTransformation_kernel << <numOfBlocks, numOfThreadsPerBlocks, 0, *ptr_stream >> >(numChannels, iw, ih, dev_src, dev_dst);

    return;
}

int main()
{

    cudaStream_t stream_one;
    cudaStream_t stream_two;
    cudaStream_t stream_three;

    cudaStreamCreate(&stream_one);
    cudaStreamCreate(&stream_two);
    cudaStreamCreate(&stream_three);

    Mat image = imread("DijSDK_test_image.jpg", 1);
    //Mat image(1080, 1920, CV_8UC3, Scalar(0,0,255));
    size_t numBytes = image.rows * image.cols * 3;
    int numChannels = 3;

    int iw = image.rows;
    int ih = image.cols;
    size_t totalMemSize = numBytes * sizeof(unsigned char);
    size_t oneThirdMemSize = totalMemSize / 3;

    unsigned char *dev_src_1, *dev_src_2, *dev_src_3, *dev_dst_1, *dev_dst_2, *dev_dst_3, *h_src, *h_dst;


    //Allocate memomry at device for SOURCE and DESTINATION and get their pointers
    cudaMalloc((void**)&dev_src_1, (totalMemSize) / 3);
    cudaMalloc((void**)&dev_src_2, (totalMemSize) / 3);
    cudaMalloc((void**)&dev_src_3, (totalMemSize) / 3);
    cudaMalloc((void**)&dev_dst_1, (totalMemSize) / 3);
    cudaMalloc((void**)&dev_dst_2, (totalMemSize) / 3);
    cudaMalloc((void**)&dev_dst_3, (totalMemSize) / 3);

    //Get the processed image 
    Mat org_dijSDK_img(image.rows, image.cols, CV_8UC3, Scalar(0, 0, 255));
    h_dst = org_dijSDK_img.data;

    //while (1)
    for (int i = 0; i < 3; i++)
    {
        std::cout << "\nLoop: " << i;

        //copy new data of image to the host pointer
        h_src = image.data;

        //Copy the source image to the device i.e. GPU
        cudaMemcpyAsync(dev_src_1, h_src, (totalMemSize) / 3, cudaMemcpyHostToDevice, stream_one);
        //KERNEL--stream-1
        callMultiStreamingCudaKernel(dev_src_1, dev_dst_1, numChannels, iw, ih, &stream_one);


        //Copy the source image to the device i.e. GPU
        cudaMemcpyAsync(dev_src_2, h_src + oneThirdMemSize, (totalMemSize) / 3, cudaMemcpyHostToDevice, stream_two);
        //KERNEL--stream-2
        callMultiStreamingCudaKernel(dev_src_2, dev_dst_2, numChannels, iw, ih, &stream_two);

        //Copy the source image to the device i.e. GPU
        cudaMemcpyAsync(dev_src_3, h_src + (2 * oneThirdMemSize), (totalMemSize) / 3, cudaMemcpyHostToDevice, stream_three);
        //KERNEL--stream-3
        callMultiStreamingCudaKernel(dev_src_3, dev_dst_3, numChannels, iw, ih, &stream_three);


        //RESULT copy: GPU to CPU
        cudaMemcpyAsync(h_dst, dev_dst_1, (totalMemSize) / 3, cudaMemcpyDeviceToHost, stream_one);
        cudaMemcpyAsync(h_dst + oneThirdMemSize, dev_dst_2, (totalMemSize) / 3, cudaMemcpyDeviceToHost, stream_two);
        cudaMemcpyAsync(h_dst + (2 * oneThirdMemSize), dev_dst_3, (totalMemSize) / 3, cudaMemcpyDeviceToHost, stream_three);

        // wait for results 
        cudaStreamSynchronize(stream_one);
        cudaStreamSynchronize(stream_two);
        cudaStreamSynchronize(stream_three);

        //Assign the processed data to the display image.
        org_dijSDK_img.data = h_dst;
        //DISPLAY PROCESSED IMAGE           
        imshow("Processed dijSDK image", org_dijSDK_img);
        waitKey(33);
    }

    cudaDeviceReset();
    return 0;
}

UPDATE-1:如果我删除第一个流的内核调用,那么第二个内核和第三个流的 H2D 副本会以某种方式重叠(不完全),如下所示。

UPDATE-2 我什至尝试使用 10 个流,但情况保持不变。第一个流的内核处理只有在第十个流数据的 H2D 副本之后才开始。

【问题讨论】:

  • 但是您显示的配置文件数据中的每个流中都发生了复制/执行重叠
  • 在执行第一个流的内核和第三个流的 H2D 副本时发生重叠。我的问题是,为什么在第一个流数据的 H2D 副本之后没有立即开始执行第一个流的内核。
  • @talonmies:第一个流内核的执行与第二个流的 H2D 数据副本之间没有重叠。那是我的问题。其余部分都很好。
  • 如果删除第一个流,第二个流是在复制数据 2 后立即开始处理还是等到数据 3 之后?
  • 主机内存是否固定? The documentation 声明必须对主机内存进行分页锁定才能发生重叠。

标签: c++ opencv cuda


【解决方案1】:

正如评论者已经指出的那样,主机内存必须是page locked

不需要通过cudaHostAlloc分配额外的主机内存,你可以在你现有的OpenCV镜像上使用cudaHostRegister

cudaHostRegister(image.data, totalMemSize, cudaHostRegisterPortable)

【讨论】:

  • 实际上,我的实际图像不会是 OpenCV 图像。我会以unsigned char 的形式获取图像数据,然后我需要在上面做这些事情。我通过在主机上分配固定内存然后使用memcpy() 将我的图像数据复制到这个固定内存来解决了这个问题。恐怕memcpy() 会不会是一种有效的方式。
  • @skm 你仍然可以使用cudaHostRegister,它不依赖于 OpenCV 图像。只需将您的 unsigned char* 指针作为第一个参数传递
  • 你能告诉我,是否也可以重叠内核执行?我尝试使用 20 个流来减小内核大小,但内核处理仍然没有重叠。
猜你喜欢
  • 1970-01-01
  • 2021-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-15
  • 2012-07-14
  • 2012-03-16
  • 2014-02-21
相关资源
最近更新 更多