试图理解在CUDA中,如果零拷贝也通过PCIe传输,为什么零拷贝速度更快?

试图理解在CUDA中,如果零拷贝也通过PCIe传输,为什么零拷贝速度更快?,cuda,Cuda,据说零拷贝应该用于满足“读取和/或写入一次”约束的情况。那很好 我明白这一点,但我的问题是,为什么零拷贝首先是快的?毕竟,无论我们使用通过cudamemcpy的显式传输还是零拷贝,在这两种情况下,数据都必须通过pci express总线传输。或者存在任何其他路径,即通过设备RAM直接复制GPU寄存器?例如,假设您编写了cuda加速编辑器算法来修复书籍的拼写错误。如果2MB文本数据只有5个字节的错误,则只需编辑其中的5个字节。因此,它不需要将整个阵列从GPU VRAM复制到系统RAM。在这里,零拷

据说零拷贝应该用于满足“读取和/或写入一次”约束的情况。那很好


我明白这一点,但我的问题是,为什么零拷贝首先是快的?毕竟,无论我们使用通过cudamemcpy的显式传输还是零拷贝,在这两种情况下,数据都必须通过pci express总线传输。或者存在任何其他路径,即通过设备RAM直接复制GPU寄存器?

例如,假设您编写了cuda加速编辑器算法来修复书籍的拼写错误。如果2MB文本数据只有5个字节的错误,则只需编辑其中的5个字节。因此,它不需要将整个阵列从GPU VRAM复制到系统RAM。在这里,零拷贝版本将只访问拥有5字节字的页面。如果没有零拷贝,它将需要拷贝整个2MB文本。复制2MB比复制5个字节或仅复制拥有这些字节的页面需要更多的时间,因此会降低每秒的图书吞吐量

另一个例子是,可以使用稀疏路径跟踪算法为游戏场景中的几个小对象添加闪亮的曲面。结果可能只需要更新10-100像素,而不是1920x1080像素。零拷贝会更好


也许稀疏矩阵乘法在零拷贝的情况下效果更好。如果8192x8192矩阵相乘,但只有3-5个元素是非零的,那么在写入结果时,零拷贝仍然会产生影响。

例如,假设您编写了cuda加速编辑器算法来修复书籍的拼写错误。如果2MB文本数据只有5个字节的错误,则只需编辑其中的5个字节。因此,它不需要将整个阵列从GPU VRAM复制到系统RAM。在这里,零拷贝版本将只访问拥有5字节字的页面。如果没有零拷贝,它将需要拷贝整个2MB文本。复制2MB比复制5个字节或仅复制拥有这些字节的页面需要更多的时间,因此会降低每秒的图书吞吐量

另一个例子是,可以使用稀疏路径跟踪算法为游戏场景中的几个小对象添加闪亮的曲面。结果可能只需要更新10-100像素,而不是1920x1080像素。零拷贝会更好


也许稀疏矩阵乘法在零拷贝的情况下效果更好。如果8192x8192矩阵相乘,但只有3-5个元素是非零的,那么在写入结果时,零拷贝仍然会产生影响。

纯粹从数据传输速率的角度考虑,在比较使用零拷贝方法移动数据与使用cudaMemcpy移动数据时,我不知道为什么通过PCIE在主机和设备之间移动数据的数据传输速率会有任何不同

然而,这两项业务都有与之相关的间接费用。我能想到的零拷贝的主要开销来自主机内存的固定。这会带来明显的时间开销,例如,与使用malloc或new分配相同数量的数据相比。cudaMemcpy的主要开销是每次传输至少几微秒的开销,这与使用执行传输的底层DMA引擎的设置成本有关

另一个区别是数据的可访问性。在主机和设备之间可以同时访问固定/零拷贝数据,这对于使用cudaMemcpyAsync可能会更加复杂的情况非常有用

这里有两个相当简单的设计模式,使用零拷贝而不是cudaMemcpy可能是有意义的

当你有大量的数据,你不知道需要什么。假设我们有一个大的数据表,比如1GB,GPU内核需要访问它。另外,假设内核设计为每个内核调用只需要表中的一个或几个位置,并且我们事先不知道这些位置是什么。我们可以使用cudaMemcpy将整个1GB传输到GPU。这当然会起作用,但可能需要相当长的时间,例如约0.1秒。还假设我们不知道更新了什么位置,在内核调用之后,我们需要访问主机上修改的数据。需要另一次调动。在这里使用pinted/zero-copy方法将基本上消除与移动数据相关的成本,并且由于我们的内核只访问少数位置,因此使用zero-copy进行访问的内核成本远低于0.1s

当您需要检查搜索或收敛算法的状态时。假设我们有一个由循环组成的算法,该循环在每次循环迭代中调用内核。内核正在执行某种搜索或收敛类型的算法,因此我们需要一个停止条件测试。这可能像布尔值一样简单,我们通过内核活动与主机通信,以指示我们是否已到达 你有没有找到停车点。如果达到停止点,循环终止。否则,循环将继续进行下一次内核启动。这里甚至可能有双向沟通。例如,主机代码可能将布尔值设置为false。如果迭代需要继续,内核可能会将其设置为true,但内核从未将该标志设置为false。因此,如果需要继续,主机代码将标志设置为false并再次调用内核。我们可以通过cudaMemcpy实现这一点:


纯粹从数据传输速率的角度考虑,在比较使用零拷贝方法移动数据与使用cudaMemcpy移动数据时,我不知道为什么通过PCIE在主机和设备之间移动数据的数据传输速率会有任何不同

然而,这两项业务都有与之相关的间接费用。我能想到的零拷贝的主要开销来自主机内存的固定。这会带来明显的时间开销,例如,与使用malloc或new分配相同数量的数据相比。cudaMemcpy的主要开销是每次传输至少几微秒的开销,这与使用执行传输的底层DMA引擎的设置成本有关

另一个区别是数据的可访问性。在主机和设备之间可以同时访问固定/零拷贝数据,这对于使用cudaMemcpyAsync可能会更加复杂的情况非常有用

这里有两个相当简单的设计模式,使用零拷贝而不是cudaMemcpy可能是有意义的

当你有大量的数据,你不知道需要什么。假设我们有一个大的数据表,比如1GB,GPU内核需要访问它。另外,假设内核设计为每个内核调用只需要表中的一个或几个位置,并且我们事先不知道这些位置是什么。我们可以使用cudaMemcpy将整个1GB传输到GPU。这当然会起作用,但可能需要相当长的时间,例如约0.1秒。还假设我们不知道更新了什么位置,在内核调用之后,我们需要访问主机上修改的数据。需要另一次调动。在这里使用pinted/zero-copy方法将基本上消除与移动数据相关的成本,并且由于我们的内核只访问少数位置,因此使用zero-copy进行访问的内核成本远低于0.1s

当您需要检查搜索或收敛算法的状态时。假设我们有一个由循环组成的算法,该循环在每次循环迭代中调用内核。内核正在执行某种搜索或收敛类型的算法,因此我们需要一个停止条件测试。这可能与布尔值一样简单,即我们从内核活动返回主机,以指示我们是否已到达停止点。如果达到停止点,循环终止。否则,循环将继续进行下一次内核启动。这里甚至可能有双向沟通。例如,主机代码可能将布尔值设置为false。如果迭代需要继续,内核可能会将其设置为true,但内核从未将该标志设置为false。因此,如果需要继续,主机代码将标志设置为false并再次调用内核。我们可以通过cudaMemcpy实现这一点:


较低的延迟…较低的延迟,因为避免了cudamemcpy函数的开销?就数据传输速率而言,它并不更快。所以这个问题的前提是可疑的。而且,如果主机和设备之间存在传输,则无法避免PCIE连接的GPU使用PCIE。没有其他路径。如果速度不够快,那么为什么要使用它?与使用cudaMemcpy相比,可能有许多情况下零拷贝可以提高应用程序的整体性能。我敢打赌,如果你去寻找,你一定能找到例子。如果你喜欢的话,我可以写一个答案,其中可能描述了两个例子。较低的延迟…由于避免了cudamemcpy函数的开销,所以较低的删除率?就数据传输速率而言,它并不更快。所以这个问题的前提是可疑的。而且,如果主机和设备之间存在传输,则无法避免PCIE连接的GPU使用PCIE。没有其他路径。如果速度不够快,那么为什么要使用它?与使用cudaMemcpy相比,可能有许多情况下零拷贝可以提高应用程序的整体性能。我敢打赌,如果你去寻找,你一定能找到例子。如果你喜欢,我可以写一个答案,其中可能描述了两个例子。
 bool *d_continue;
 cudaMalloc(&d_continue, sizeof(bool));
 bool h_continue = true;
 while (h_continue){
   h_continue = false;
   cudaMemcpy(d_continue, &h_continue, sizeof(bool), cudaMemcpyHostToDevice); 
   my_search_kernel<<<...>>>(..., d_continue);
   cudaMemcpy(&h_continue, d_continue, sizeof(bool), cudaMemcpyDeviceToHost);
 }
 bool *z_continue;
 cudaHostAlloc(&z_continue, sizeof(bool), ...);
 *z_continue = true;
 while (*z_continue){
   *z_continue = false;
   my_search_kernel<<<...>>>(..., z_continue);
   cudaDeviceSynchronize();
 }