首页 > 解决方案 > OpenCL 内存传输函数如何工作?

问题描述

我有几个与 OpenCL 内存传输函数有关的问题。我遇到了很多与此相关的问题,但没有一个给出扩展答案。或许我们可以在这里收集总体答案。

这是我目前对三种当前移动数据方式的看法:

1) enqueueReadBuffer/enqueueWriteBuffer - 这两个函数总是将在主机上创建的缓冲区的内容复制到设备,然后从设备复制。这里没有使用固定内存和 DMA 机制。

2) enqueueMigrateMemObjects - 这有时被描述为 enqueueRead/Write 的替代方案,但在这种情况下,内存在此函数调用时被精确复制。这里没有使用固定内存和 DMA 机制。

3) enqueueMapBuffer/enqueueUnmapBuffer - 这里总是使用固定内存和 DMA 机制。

此函数使用两种类型的缓冲区:使用 CL_MEM_USE_HOST_PTR 标志或 CL_MEM_ALLOC_HOST_PTR 标志创建。对于第一个,我们将在主机上创建的数组映射到在设备上创建的数组。在设备上分配第二个数组并将其映射到主机上新创建的数组。

这是我可以根据文档说明的。我进行了几次测试,但只看到迁移功能比读/写更快。关于这三段,我有一些问题:

1)如果这些功能只做复制,那么为什么在这里https://software.intel.com/en-us/forums/opencl/topic/509406人们谈论在读/写期间固定/取消固定内存?这些函数在什么条件下使用固定内存?或者这只是英特尔实现的功能,所有与内存传输相关的功能都使用固定内存和 DMA?

另外,这是否意味着,如果我使用固定内存,那么 DMA 机制会起作用吗?反之亦然 - 如果我想让 DMA 工作,我需要固定内存吗?

2) 这个迁移函数 - 正是 enqueueRead/WriteBuffer 函数内部发生的事情,没有一些额外的开销,这些 enqueuRead/writeBuffer 函数给出了?它总是只是复制还是 DMA 传输?

由于某些原因,一些消息来源在谈论 DMA 传输时,使用“复制”、“内存”、“迁移”字样在两个缓冲区(在主机和设备上)之间传输数据。但是,不可能有任何副本,我们只是直接写入缓冲区,根本没有任何副本。在 DMA 期间我应该如何处理这个写入?

如果我将 enqueueMigrateMemOjects 与使用标志 CL_MEM_USE_HOST_PTR 创建的缓冲区一起使用,会发生什么?

3)使用这两个功能,完全混乱。如果我使用:a) 现有主机指针或 b) 新分配的主机指针,映射和读/写将如何发生?

同样在这里,我没有正确理解 DMA 的工作原理。如果我将主机端的缓冲区映射到设备端的缓冲区,则借助哪些函数可以在它们之间传输内存?之后我应该总是取消映射缓冲区吗?

对此没有任何解释,例如:“当我们使用此标志创建一个新缓冲区并使用此内存函数传输时,数据以这种方式传输并使用诸如...之类的功能。如果将内存创建为读取-only,如果内存只写,就会发生这种情况 - this”。

也许已经有一个很好的指南,但是从 OpenCL 规范来看,我无法回答我的问题。

标签: memoryopencldma

解决方案


1) DMA 用于所有数据传输命令,但它仅适用于固定内存区域。否则操作系统只会将其分页并提供虚假数据。

在 enqueueReadBuffer/enqueueWriteBuffer 中,数据首先进入已经固定(或及时固定,我不知道)的内部缓冲区,然后使用 DMA 进入 GPU 内存。因为 GPU 可以在页面中发送或接收数据,例如 4096 的整数倍大小,起始地址为 4096 的倍数等(取决于供应商的对齐规则)。ReadBuffer 和 WriteBuffer 可能会更慢,因为它会执行两份副本,一份从主机数组到内部数组,然后是内部固定数组到 gpu 缓冲区。

在 enqueueMapBuffer/enqueueUnmapBuffer 中,它可以直接有效地进行 DMA 传输,因为它会刷新仅从主机端使用的页面(您写入主机阵列但由于它被映射,因此它被上传到 gpu 缓冲区),因为它临时固定地区,只有一次

使用 CL_MEM_ALLOC_HOST_PTR 或 CL_MEM_USE_HOST_PTR 只能是一种优化,可以跳过内部(固定)数组复制步骤,但要确保它们符合要求。不能保证总是得到固定数组,它们是一种相当稀缺的资源。固定意味着操作系统不会随时将其分页。您也可能只是“触摸”到缓冲区的页面(4kB?)区域的第一个字节以伪造“固定”(在调用数据传输函数之前),但这是不合法的,并且可能并不总是有效。但是我在我的 OpenCL 应用程序中观察到加速,只需在 USE_HOST_PTR 标记的缓冲区上提供一个好的偏移量和一个好的复制大小(如 4k 对齐和 64k 大小)。(仅在 AMD、NVIDIA GPU 和 Intel IGPU 上尝试过,但不能说有关 Xeon Phi 设备的任何信息)

您只需要固定内存来跳过额外的复制开销。您需要 map/unmap 来优化固定。

2) 迁移功能将一个 GPU 缓冲区的所有权迁移到另一个 GPU。如果你在同一个 GPU 上使用它,除了将自身复制到 RAM 然后再将自身复制回来之外,它不应该有任何用处。如果您使用CL_MIGRATE_MEM_OBJECT_- CONTENT_UNDEFINED ,则它不会复制数据。只需将所有权转移到其他设备即可。然后你可以自己复制数据(如果你的意思是想要的数据在主机上,而不是源设备上)

一些实现直接通过 pci-e 复制其数据(我不知道这是否使用 GPU1 DMA 到 GPU2 DMA,但我猜是的)和一些实现使用双 DMA 操作通过 RAM(但它可以通过流水线在某种程度上进行优化?)

如果我将 enqueueMigrateMemOjects 与使用标志 CL_MEM_USE_HOST_PTR 创建的缓冲区一起使用,会发生什么?

我没有尝试但猜测它只会移动所有权,因为数据仅在主机上。您提供的英特尔链接还包括有人说“迁移触发 DMA 操作”。当使用两个 Phi 之间的迁移时,默认情况下,可能两个 Intel Xeon Phi 与 DMA 通信,而不是通过系统 RAM。

3)CL_MEM_USE_HOST_PTR旨在使用您的应用程序的指针。它们可以是任何东西,甚至是固定的(但在 OpenCL 的内部规则之外,这可能并不总是好的)。CL_MEM_ALLOC_HOST_PTR如果可以的话,意味着使用 OpenCL 实现的固定内存。如果您只使用 CL_MEM_READ_WRITE 那么它在设备内存上。使用CL_MEM_USE_HOST_PTRandCL_MEM_ALLOC_HOST_PTR意味着如果 OpenCL 内核直接与 CPU 共享 RAM(例如,如果设备是 iGPU),它将执行零拷贝访问。没有固定,额外的副本。通过固定,iGPU 不会复制。非常大的区别。但是对于一个独立的 GPU(或像 Xeon Phi 这样的计算卡),它可能是额外副本版本的速度的 1.0 倍到 2.0 倍之间(考虑到主机到主机和主机到设备的副本具有相似的带宽)。

映射意味着主机端映射。主机看到设备内存“映射”到它自己的。所以设备无法访问它(例如通过内核)。写入意味着主机端写入。主机写入 GPU 内存。阅读意味着主人阅读。主机从 GPU 内存读取。所以,map unmap 是这样发生的:

CL_MAP_WRITE_INVALIDATE_REGION版本:

  • 您将主机指针(由 map 命令返回)映射到设备缓冲区
    • 现在缓冲区所有权在主机上
  • 使用 GPU 缓冲区,就好像它是这个主机指针一样
  • 取消映射该区域,以便将最新位刷新到 GPU 内存(或者,如果它是 iGPU,则无操作!!)
    • 现在缓冲区所有权在设备上

还有另一种 ( CL_MAP_WRITE) 用法

  • 你事先准备好数据
  • map(启用初始复制)(获取所有数据)//额外开销
  • 可选元素“更新”
  • 取消映射
  • 现在数据(可选更新)在 GPU 内存上
  • 以便 OpenCL 内核函数可以将其用作参数

在 map 和 unmap 之间,它可以将您提供的任何主机输入刷新到 GPU 内存中,因为它一次固定整个区域。否则它将(如在 enqueueWriteBuffer 中)需要取消固定任何当前数据(如单个整数)(及其整个页面)被发送到 GPU 并且它会很慢。

当您复制 1GB 缓冲区时,内存消耗不会达到 2GB。它在较小的内部阵列上处理 pin-unpin-extra-copy 操作,例如 64kB,但取决于 OpenCL 的供应商实现。

但是在 enqueueWriteBuffer 上,它使用内部缓冲区进行必要的复制和固定。固定和取消固定以及进行额外的复制都会使 enqueueWriteBuffer 变慢,但是您仍然可以尝试为其提供正确对齐的主机指针和适当大小的区域以更快地进行复制。至少这会让它在后台对整个数组进行一次固定。如果实现有优化,甚至可能会跳过额外的副本。


推荐阅读