cudaMemcpy过慢。

11
我使用cudaMemcpy()一次将精确地1GB的数据复制到设备上。这需要5.9秒。而反过来只需要5.1秒。这正常吗?
这个函数本身在复制之前有这么多开销吗? 理论上,PCIe总线的吞吐量至少为4GB/s。
由于Tesla C870不支持内存传输重叠,所以没有内存传输重叠。有什么提示吗?
编辑2:我的测试程序+更新时间;希望读起来不会太麻烦!
cutCreateTimer()函数无法编译通过:“错误:未定义标识符“cutCreateTimer””,这可能与安装在机器上的旧CUDA版本(2.0)有关。
 __host__ void time_int(int print){
static struct timeval t1; /* var for previous time stamp */
static struct timeval t2; /* var of current time stamp */
double time;
if(gettimeofday(&t2, 0) == -1) return;
if(print != 0){
  time = (double) (t2.tv_sec - t1.tv_sec) + ((double) (t2.tv_usec - t1.tv_usec)) / 1000000.0;
  printf(...);
}
t1 = t2;
}

main:
time(0);
void *x;
cudaMallocHost(&x,1073741824);
void *y;
cudaMalloc(&y, 1073741824);
time(1);
cudaMemcpy(y,x,1073741824, cudaMemcpyHostToDevice);
time(1);
cudaMemcpy(x,y,1073741824, cudaMemcpyDeviceToHost);
time(1);

显示的时间如下:
0.86秒 分配
0.197秒 第一次复制
5.02秒 第二次复制
奇怪的是:尽管第一次复制显示为0.197秒,但如果我观察程序运行时,它需要更长的时间。


你能在问题中添加一些关于如何进行时间测量的信息吗? - talonmies
@talonmies: 在编辑中描述了时间。 - Callahan
3个回答

12

是正常的。 cudaMemcpy() 做了很多检查和工作(如果主机内存是通过通常的malloc()mmap() 分配的)。 它应该检查数据的每一页是否在内存中,并将这些页面(逐个)移动到驱动程序中。

您可以使用cudaHostAlloc函数cudaMallocHost来分配内存,而不是使用malloc。它会分配固定内存,始终存储在RAM中,并且可以直接由GPU的DMA访问(更快的cudaMemcpy())。引用第一个链接:

分配 count 字节的主机内存,该内存已锁定页并可被设备访问。 驱动程序跟踪使用此函数分配的虚拟内存范围,并自动加速对函数(如cudaMemcpy())的调用。

唯一的限制因素是系统中固定内存的总量受限(不超过RAM大小;最好不要使用超过RAM - 1Gb):

分配过多的固定内存可能会降低系统性能,因为它减少了系统用于分页的内存量。 因此,最好仅适度使用此函数来为主机和设备之间的数据交换分配暂存区。


好的,“上传”(到设备)降至1.1秒,但“下载”(从设备)仍然为5秒。这也正常吗? - Callahan
下载到固定内存了吗? - osgx
是的。在这个测试中,我从相同的内存下载到了我上传的内容。 - Callahan
Callahan,请您发布代码的详细信息,包括以下内容:内存分配;上传;启动内核(以及内核源代码);下载;时间记录。CUDA 的正确计时方法是使用 CUDA 的定时器,并检查附带 CUDA 的代码示例。或者在这里查看:https://www.cs.virginia.edu/~csadmin/wiki/index.php/CUDA_Support/Measuring_kernel_runtime - osgx
显示剩余2条评论

6
假设传输时间准确,从固定内存传输1 GB需要1.1秒似乎有点慢。您确定PCIe插槽已配置为正确的宽度吗?要获得完全性能,您需要x16配置。某些平台提供两个插槽,其中一个配置为x16,另一个配置为x4。因此,如果您的机器有两个插槽,您可能需要尝试将卡移至另一个插槽。其他系统有两个插槽,如果只有一个插槽被占用,则可以获得x16,但如果两个插槽都被占用,则可以获得两个x8插槽。 BIOS设置可能有助于确定PCIe插槽的配置。
Tesla C870是相当老的技术,但如果我记得正确,这些部件应该可以通过第一代PCIe接口实现大约2 GB / s的传输速率。当前的Fermi级GPU使用PCIe gen 2接口,可以实现从固定内存传输5+ GB / s(对于吞吐量测量,1 GB / s = 10 ^ 9字节/秒)。
请注意,PCIe使用分组传输,并且在常见芯片组支持的数据包大小下,数据包开销可能非常大,新型芯片组通常支持较长的数据包。即使对于从/到固定主机内存的传输,也不太可能超过名义上的每个方向的最大值的70%(PCIe 1.0 x16为4 GB / s,PCIe 2.0 x16为8 GB / s)。这是一篇白皮书,解释了开销问题,并提供了一个方便的图表,显示了各种数据包大小可实现的利用率:http://www.plxtech.com/files/pdf/technical/expresslane/Choosing_PCIe_Packet_Payload_Size.pdf

关于下载速度变慢(卡->主机),即使钉住在内存中,您有什么评论? - osgx
不幸的是,我无法进入机器或BIOS,因为我正在远程操作它。 - Callahan

1

除了系统没有正确配置,可怕的PCIe带宽最好的解释是IOH/插槽与GPU插入的PCIe插槽之间不匹配。

大多数多插槽的Intel i7级别(Nehalem、Westmere)主板每个插槽都有一个I/O集线器。由于系统内存直接连接到每个CPU,DMA访问“本地”(从连接到与执行DMA访问的GPU相同IOH的CPU获取内存)要比非本地访问(从连接到另一个IOH的CPU获取内存,通过链接两个CPU的QPI互连满足的交易)快得多。

重要说明:不幸的是,SBIOS常常将系统配置为交错,这会导致连续的内存分配在插槽之间交错。这样做可以缓解CPU的本地/非本地访问性能崖,但对GPU访问数据造成了破坏,因为在2插槽系统中,每隔一页就是非本地的。

Nehalem和Westmere级别的系统似乎不会在系统只有一个IOH时受到此问题的影响。

顺便提一下,Sandy Bridge级处理器通过将PCI Express支持集成到CPU中,进一步沿着这条道路迈出了一步,因此使用Sandy Bridge的多插槽机器自动具有多个IOH。

您可以通过使用将其固定到套接字的工具(如果可用,则在Linux上使用numactl)或使用平台相关代码来引导分配和线程以在特定套接字上运行来调查此假设。 您可以在不花哨的情况下学到很多-只需在main()开头调用具有全局效果的函数,将所有内容强制转移到一个套接字或另一个套接字,并查看是否对PCIe传输性能产生重大影响。


我在开头执行了一个 cudaSetDevice(0); 作为“全局效果”函数。时间没有改变。 - Callahan

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