Jetson TK1上的CUDA零拷贝与CudaMemcpy的比较

3

我的问题是:我正在寻找有人能指出我在尝试在CUDA中使用零拷贝时的错误,或者揭示一种更“幕后”的方法来解释为什么零拷贝方法不比memcpy方法更快。顺便说一下,我正在NVidia的TK1处理器上执行我的测试,使用Ubuntu。

我的问题与有效地使用NVIDIA TK1的(物理上)统一内存架构与CUDA有关。 NVIDIA提供了两种GPU/CPU内存传输抽象方法:

  1. 统一内存抽象(使用cudaHostAlloc和cudaHostGetDevicePointer)
  2. 显式从主机复制和从设备复制(使用cudaMalloc()和cudaMemcpy)

我的测试代码简述:我使用两种方法测试同一个cuda核心。鉴于没有源数据的设备复制或结果数据的设备复制,我预计方法1会更快。然而,结果与我的假设相反(方法# 1慢50%)。以下是此测试的代码:

#include <libfreenect/libfreenect.hpp>
#include <iostream>
#include <vector>
#include <cmath>
#include <pthread.h>
#include <cxcore.h>
#include <time.h>
#include <sys/time.h>
#include <memory.h>
///CUDA///
#include <cuda.h>
#include <cuda_runtime.h>

 ///OpenCV 2.4
#include <highgui.h>
#include <cv.h>
#include <opencv2/gpu/gpu.hpp>

using namespace cv;
using namespace std;

///The Test Kernel///
__global__ void cudaCalcXYZ( float *dst, float *src, float *M, int height, int width, float scaleFactor, int minDistance)
{
    float nx,ny,nz, nzpminD, jFactor;
    int heightCenter = height / 2;
    int widthCenter = width / 2;
    //int j = blockIdx.x;   //Represents which row we are in
    int index = blockIdx.x*width;
    jFactor = (blockIdx.x - heightCenter)*scaleFactor;
    for(int i= 0; i < width; i++)
    {
        nz = src[index];
        nzpminD = nz + minDistance;
        nx = (i - widthCenter )*(nzpminD)*scaleFactor;      
        ny = (jFactor)*(nzpminD);   
        //Solve for only Y matrix (height vlaues)           
         dst[index++] = nx*M[4] + ny*M[5] + nz*M[6];
        //dst[index++] = 1 + 2 + 3;
    }
}

//Function fwd declarations
double getMillis();
double getMicros();
void runCudaTestZeroCopy(int iter, int cols, int rows);
void runCudaTestDeviceCopy(int iter, int cols, int rows);

int main(int argc, char **argv) {

    //ZERO COPY FLAG (allows runCudaTestZeroCopy to run without fail)
    cudaSetDeviceFlags(cudaDeviceMapHost);

    //Runs kernel using explicit data copy to 'device' and back from 'device'
    runCudaTestDeviceCopy(20, 640,480);
    //Uses 'unified memory' cuda abstraction so device can directly work from host data
    runCudaTestZeroCopy(20,640, 480);

    std::cout << "Stopping test" << std::endl;

    return 0;
}

void runCudaTestZeroCopy(int iter, int cols, int rows)
{
    cout << "CUDA Test::ZEROCOPY" << endl;
        int src_rows = rows;
        int src_cols = cols;
        int m_rows = 4;
        int m_cols = 4;
        int dst_rows = src_rows;
        int dst_cols = src_cols;
        //Create and allocate memory for host mats pointers
        float *psrcMat;
        float *pmMat;
        float *pdstMat;
        cudaHostAlloc((void **)&psrcMat, src_rows*src_cols*sizeof(float), cudaHostAllocMapped);
        cudaHostAlloc((void **)&pmMat, m_rows*m_cols*sizeof(float), cudaHostAllocMapped);
        cudaHostAlloc((void **)&pdstMat, dst_rows*dst_cols*sizeof(float), cudaHostAllocMapped);
        //Create mats using host pointers
        Mat src_mat = Mat(cvSize(src_cols, src_rows), CV_32FC1, psrcMat);
        Mat m_mat   = Mat(cvSize(m_cols, m_rows), CV_32FC1, pmMat);
        Mat dst_mat = Mat(cvSize(dst_cols, dst_rows), CV_32FC1, pdstMat);

        //configure src and m mats
        for(int i = 0; i < src_rows*src_cols; i++)
        {
            psrcMat[i] = (float)i;
        }
        for(int i = 0; i < m_rows*m_cols; i++)
        {
            pmMat[i] = 0.1234;
        }
        //Create pointers to dev mats
        float *d_psrcMat;
        float *d_pmMat;
        float *d_pdstMat;
        //Map device to host pointers
        cudaHostGetDevicePointer((void **)&d_psrcMat, (void *)psrcMat, 0);
        //cudaHostGetDevicePointer((void **)&d_pmMat, (void *)pmMat, 0);
        cudaHostGetDevicePointer((void **)&d_pdstMat, (void *)pdstMat, 0);
        //Copy matrix M to device
        cudaMalloc( (void **)&d_pmMat, sizeof(float)*4*4 ); //4x4 matrix
        cudaMemcpy( d_pmMat, pmMat, sizeof(float)*m_rows*m_cols, cudaMemcpyHostToDevice);

        //Additional Variables for kernels
        float scaleFactor = 0.0021;
        int minDistance = -10;

        //Run kernel! //cudaSimpleMult( float *dst, float *src, float *M, int width, int height)
        int blocks = src_rows;
        const int numTests = iter;
        double perfStart = getMillis();

        for(int i = 0; i < numTests; i++)
        {           
            //cudaSimpleMult<<<blocks,1>>>(d_pdstMat, d_psrcMat, d_pmMat, src_cols, src_rows);
            cudaCalcXYZ<<<blocks,1>>>(d_pdstMat, d_psrcMat, d_pmMat, src_rows, src_cols, scaleFactor, minDistance);
            cudaDeviceSynchronize();
        }
        double perfStop = getMillis();
        double perfDelta = perfStop - perfStart;
        cout << "Ran " << numTests << " iterations totaling " << perfDelta << "ms" << endl;
        cout << " Average time per iteration: " << (perfDelta/(float)numTests) << "ms" << endl;

        //Copy result back to host
        //cudaMemcpy(pdstMat, d_pdstMat, sizeof(float)*src_rows*src_cols, cudaMemcpyDeviceToHost);
        //cout << "Printing results" << endl;
        //for(int i = 0; i < 16*16; i++)
        //{
        //  cout << "src[" << i << "]= " << psrcMat[i] << " dst[" << i << "]= " << pdstMat[i] << endl;
        //}

        cudaFree(d_psrcMat);
        cudaFree(d_pmMat);
        cudaFree(d_pdstMat);
        cudaFreeHost(psrcMat);
        cudaFreeHost(pmMat);
        cudaFreeHost(pdstMat);
}

void runCudaTestDeviceCopy(int iter, int cols, int rows)
{
        cout << "CUDA Test::DEVICE COPY" << endl;
        int src_rows = rows;
        int src_cols = cols;
        int m_rows = 4;
        int m_cols = 4;
        int dst_rows = src_rows;
        int dst_cols = src_cols;
        //Create and allocate memory for host mats pointers
        float *psrcMat;
        float *pmMat;
        float *pdstMat;
        cudaHostAlloc((void **)&psrcMat, src_rows*src_cols*sizeof(float), cudaHostAllocMapped);
        cudaHostAlloc((void **)&pmMat, m_rows*m_cols*sizeof(float), cudaHostAllocMapped);
        cudaHostAlloc((void **)&pdstMat, dst_rows*dst_cols*sizeof(float), cudaHostAllocMapped);
        //Create pointers to dev mats
        float *d_psrcMat;
        float *d_pmMat;
        float *d_pdstMat;
        cudaMalloc( (void **)&d_psrcMat, sizeof(float)*src_rows*src_cols ); 
        cudaMalloc( (void **)&d_pdstMat, sizeof(float)*src_rows*src_cols );
        cudaMalloc( (void **)&d_pmMat, sizeof(float)*4*4 ); //4x4 matrix
        //Create mats using host pointers
        Mat src_mat = Mat(cvSize(src_cols, src_rows), CV_32FC1, psrcMat);
        Mat m_mat   = Mat(cvSize(m_cols, m_rows), CV_32FC1, pmMat);
        Mat dst_mat = Mat(cvSize(dst_cols, dst_rows), CV_32FC1, pdstMat);

        //configure src and m mats
        for(int i = 0; i < src_rows*src_cols; i++)
        {
            psrcMat[i] = (float)i;
        }
        for(int i = 0; i < m_rows*m_cols; i++)
        {
            pmMat[i] = 0.1234;
        }

        //Additional Variables for kernels
        float scaleFactor = 0.0021;
        int minDistance = -10;

        //Run kernel! //cudaSimpleMult( float *dst, float *src, float *M, int width, int height)
        int blocks = src_rows;

        double perfStart = getMillis();
        for(int i = 0; i < iter; i++)
        {           
            //Copty from host to device
            cudaMemcpy( d_psrcMat, psrcMat, sizeof(float)*src_rows*src_cols, cudaMemcpyHostToDevice);
            cudaMemcpy( d_pmMat, pmMat, sizeof(float)*m_rows*m_cols, cudaMemcpyHostToDevice);
            //Run Kernel
            //cudaSimpleMult<<<blocks,1>>>(d_pdstMat, d_psrcMat, d_pmMat, src_cols, src_rows);
            cudaCalcXYZ<<<blocks,1>>>(d_pdstMat, d_psrcMat, d_pmMat, src_rows, src_cols, scaleFactor, minDistance);
            //Copy from device to host
            cudaMemcpy( pdstMat, d_pdstMat, sizeof(float)*src_rows*src_cols, cudaMemcpyDeviceToHost);
        }
        double perfStop = getMillis();
        double perfDelta = perfStop - perfStart;
        cout << "Ran " << iter << " iterations totaling " << perfDelta << "ms" << endl;
        cout << " Average time per iteration: " << (perfDelta/(float)iter) << "ms" << endl;

        cudaFree(d_psrcMat);
        cudaFree(d_pmMat);
        cudaFree(d_pdstMat);
        cudaFreeHost(psrcMat);
        cudaFreeHost(pmMat);
        cudaFreeHost(pdstMat);
}

//Timing functions for performance measurements
double getMicros()
{
    timespec ts;
    //double t_ns, t_s;
    long t_ns;
    double t_s;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    t_s = (double)ts.tv_sec;
    t_ns = ts.tv_nsec;
    //return( (t_s *1000.0 * 1000.0) + (double)(t_ns / 1000.0) );
    return ((double)t_ns / 1000.0);
}

double getMillis()
{
    timespec ts;
    double t_ns, t_s;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    t_s = (double)ts.tv_sec;
    t_ns = (double)ts.tv_nsec;
    return( (t_s * 1000.0) + (t_ns / 1000000.0) );
}

我已经看过这篇文章:Cuda zero-copy performance,但我认为它与以下原因无关:GPU和CPU具有物理上统一的内存架构。

谢谢。


2
[SO] 不是一个讨论论坛,这个问题也不太适合在这里提问。如果您有一个具体的独立问题,并且有一个简短的完整代码示例来说明您的问题,请将其编辑到您的问题中。放置谷歌驱动器链接到代码是适得其反的。如果链接失效,问题就毫无意义了。问题和答案存在作为永久记录,以帮助您和未来遇到相同问题或问题的访问者。我投票关闭了这个问题。 - talonmies
谢谢您的建议,我将删除“讨论”的请求,并更明确地提出底线要求,因为我的问题是基于我提供的两种方法,特别是“如何在物理统一内存架构上高效使用零拷贝?” - dwyer2bp
2个回答

1
ZeroCopy功能使我们能够在设备上运行数据,而无需像cudaMemcpy函数那样手动将其复制到设备内存中。零拷贝内存只传递主机地址到在内核设备上读写的设备。因此,您在声明内核设备时声明的线程块越多,内核设备上读/写的数据就越多,传递给设备的主机地址也就越多。最终,您可以比仅向设备内核声明几个线程块获得更好的性能提升。

1
当您使用ZeroCopy时,对内存的读取经过某些路径,在该路径中,它会查询内存单元以从系统内存中获取数据。这个操作具有一定的延迟。
当使用直接访问内存时,内存单元从全局内存中收集数据,并具有不同的访问模式和延迟。
实际上,要看到这种差异需要进行某种形式的分析。
尽管如此,您对全局函数的调用只使用了一个线程。
cudaCalcXYZ<<< blocks,1 >>> (...

在这种情况下,当从系统内存(或全局内存)中获取内存时,GPU很难隐藏延迟。我建议您使用更多的线程(64的倍数,至少128个),并对其进行分析以获取内存访问成本。您的算法似乎是可分离的,并且可以修改代码。
for(int i= 0; i < width; i++)

for (int i = threadIdx.x ; i < width ; i += blockDim.x)

可能会提高整体性能。 图像尺寸为640宽,将转化为5个128线程的迭代。

cudaCalcXYZ<<< blocks,128 >>> (...

我相信这将会带来一些性能提升。

我认为你的做法是正确的,增加更多线程以便每个像素有一个线程(480个块,640个线程),这可以稍微提升零拷贝技术。但是,在我的零拷贝例程中,我将4x4矩阵制成了一种设备复制方法,而较大的输入和输出矩阵仍然采用了零拷贝技术。现在性能显著改善(执行时间从8ms减少到1ms)。我应该上传新代码以展示这种性能提升吗?我仍然不明白为什么小矩阵上的零拷贝技术会造成访问延迟方面的如此大差别。 - dwyer2bp
我还注意到,使用cudaHostAlloc分配的内存,在从CPU线程上进行操作时会有显著的延迟惩罚。这种情况常见吗? - dwyer2bp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接