我在
与图形编程相关的技术论文。我一直在寻找一个简单而清晰的解释,但发现大多是这种类型的“硬核”论文。我很高兴收到外行风格的回答,关于 GPU 架构上的相干内存实际上是什么以及它是如何实现的与其他(可能不连贯的)内存类型进行比较。
记忆就是记忆。但是不同的东西可以访问该内存。GPU 可以访问内存,CPU 可以访问内存,或者其他硬件位,等等。
如果其他人对该记忆所做的更改对读者可见,则该特定事物具有对记忆的“连贯”访问。现在,你可能认为这是愚蠢的。毕竟,如果记忆被改变了,怎么可能有人看不到呢?
简单地说,缓存。
事实证明,改变内存是昂贵的。所以我们尽一切可能避免改变记忆,除非我们绝对必须这样做。当您从 CPU 将单个字节写入内存中的指针时,CPU 还没有写入该字节。或者至少,不记得。它将其写入该内存的本地副本,称为“缓存”。
这样做的原因是,一般来说,应用程序不会写入(或读取)单个字节。他们更有可能以小块的形式写入(和读取)大量字节。因此,如果您要执行诸如内存加载或存储之类的昂贵操作,则应该加载或存储大量内存。因此,您将要对一块内存进行的所有更改都存储在缓存中,然后在将来的某个时间将该缓存块写入实际内存。
但是,如果您有两个使用相同内存的独立设备,则需要某种方法来确保一个设备所做的写入对其他设备可见。大多数 GPU 无法读取 CPU 缓存。而且大多数 CPU 语言没有语言级别的支持来说“嘿,我写到内存中的那些东西?我真的是想让你现在把它写到内存中。” 因此,您通常需要一些东西来确保更改的可见性。
在 Vulkan 中,标记为的内存VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
意味着,如果您读/写该内存(通过映射指针,因为这是 Vulkan 让您直接写入内存的唯一方式),您不需要使用函数vkInvalidateMappedMemoryRanges
/vkFlushMappedMemoryRanges
来确保CPU/GPU 可以看到这些变化。任何更改的可见性都可以在两个方向上得到保证。如果该标志在内存中不可用,那么您必须使用上述功能来确保您要访问的特定数据区域的一致性。
对于相干内存,硬件方面会发生两件事之一。CPU 对内存的访问没有缓存在任何 CPU 的缓存中,或者 GPU 可以直接访问 CPU 的缓存(可能是因为与 CPU 在同一个芯片上)。您通常可以看出后者正在发生,因为 Vulkan 的片上 GPU 实现不会费心提供不连贯的内存选项。
如果内存是连贯的,那么访问该内存的所有线程必须始终就内存的状态达成一致,例如:如果线程 0 读取内存位置 A 并且线程 1 同时读取相同的位置,则两个线程应始终读取相同的价值。
但是如果内存不连贯,那么线程 A 和 B 可能会读回不同的值。线程 0 可能认为位置 A 包含 1,而线程认为该位置包含 2。不同的线程可能对内存有不连贯的看法。
使用大量内核很难实现连贯性。通常每个内核都必须知道来自所有其他内核的内存访问。因此,如果您在四核 CPU 中有 4 个核心,那么一致性并不难实现,因为每个核心都必须了解其他 3 个核心的内存访问地址,但是在具有 16 个核心的 GPU 中,必须让每个核心都知道其他 15 个内核访问内存。核心使用所谓的“缓存一致性协议”交换有关其缓存内容的数据。
这就是为什么 GPU 通常只支持有限形式的一致性。如果某些内存位置是只读的或仅由单个线程访问,则不需要一致性。如果缓存很小并且并不总是需要一致性,而只是在程序的特定指令处,那么可以在特定内存访问之前或之后使用缓存刷新来实现程序的正确行为。
如果您的硬件同时提供相干和非相干内存类型,那么您可以预期非相干内存会更快,但如果您尝试使用此内存运行并行算法,它们将以非常奇怪的方式失败。