什么是 GPU 上的相干内存?
What is coherent memory on GPU?
我在
中一次也没有遇到 "non coherent" 和 "coherent" 的记忆
tech papers related to graphics programming.I have been searching for a simple and clear explanation,but found mostly 'hardcore' papers of this type.I 很高兴收到关于 GPU 架构上的相干内存实际上是什么以及它与其他(可能不相干的)内存类型的比较的外行人风格的答案。
记忆就是记忆。但是不同的东西可以访问该内存。 GPU 可以访问内存,CPU 可以访问内存,也许其他硬件位,无论如何。
如果其他人对该内存所做的更改对 reader 可见,则该特定事物具有对内存的“一致”访问。现在,你可能认为这是愚蠢的。毕竟,如果内存被更改了,怎么可能有人可能看不到?
简单地说,缓存。
原来换内存的代价很大。因此,我们尽一切可能 避免 更改内存,除非我们绝对必须这样做。当您将 CPU 中的单个字节写入内存中的指针时,CPU 尚未写入该字节。或者至少,不是为了记忆。它将它写入该内存的本地副本,称为“缓存”。
这是因为,一般来说,应用程序不会写入(或读取)单个字节。他们更有可能以小块的形式写入(和读取)大量字节。因此,如果您要执行诸如内存加载或存储之类的昂贵操作,则应该加载或存储大块内存。因此,您将要对缓存中的内存块进行的所有更改存储起来,然后在将来的某个时间将该缓存块写入实际内存。
但是,如果您有两个使用相同内存的独立设备,则需要一些方法来确保一个设备所做的写入对其他设备可见。大多数 GPU 无法读取 CPU 缓存。大多数 CPU 语言都没有语言级别的支持,无法说“嘿,我写到内存中的那些东西?我的意思是让你现在就把它写到内存中。”因此,您通常需要一些东西来确保更改的可见性。
在 Vulkan 中,由 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
标记的内存意味着,如果您 read/write 该内存(通过映射指针,因为这是 Vulkan 允许您直接写入内存的唯一方式),您 不需要 使用函数 vkInvalidateMappedMemoryRanges
/vkFlushMappedMemoryRanges
来确保 CPU/GPU 可以看到这些更改。任何更改的可见性在两个方向上都得到保证。如果该标志在内存中不可用,则必须使用上述函数来确保要访问的特定数据区域的一致性。
对于相干内存,硬件方面的两件事之一正在发生。 CPU 对内存的访问未缓存在任何 CPU 的缓存中,或者 GPU 可以直接访问 CPU 的缓存(可能是因为在同一个作为 CPU(s)) 死去。您通常可以看出后者正在发生,因为 Vulkan 的片上 GPU 实现不会费心提供非一致性内存选项。
如果内存是一致的,那么访问该内存的所有线程必须始终就内存状态达成一致,例如:如果线程 0 读取内存位置 A 而线程 1 同时读取同一位置,则两个线程应始终读取相同的值。
但是如果内存不一致,那么线程 A 和 B 可能会读回不同的值。线程 0 可能认为位置 A 包含一个 1,而线程认为该位置包含一个 2。不同的线程对内存的看法可能不一致。
大量内核很难实现连贯性。通常每个核心都必须知道来自所有其他核心的内存访问。因此,如果您在四核中有 4 个内核 CPU,则一致性并不难实现,因为每个内核都必须了解其他 3 个内核的内存访问地址,但在具有 16 个内核的 GPU 中,每个内核都必须了解其他 15 个内核的内存访问。核心使用所谓的 "cache coherence protocols" 交换有关其缓存内容的数据。
这就是为什么 GPU 通常只支持有限形式的一致性。如果某些内存位置是只读的或仅由单个线程访问,则不需要一致性。如果高速缓存很小并且并不总是需要一致性,而只是在程序的特定指令处才需要,那么可以在特定内存访问之前或之后使用高速缓存刷新来实现程序的正确行为。
如果您的硬件同时提供连贯和非连贯内存类型,那么您可以预期非连贯内存会更快,但如果您尝试 运行 使用此内存的并行算法,它们将失败真的很奇怪的方式。
我在
中一次也没有遇到 "non coherent" 和 "coherent" 的记忆tech papers related to graphics programming.I have been searching for a simple and clear explanation,but found mostly 'hardcore' papers of this type.I 很高兴收到关于 GPU 架构上的相干内存实际上是什么以及它与其他(可能不相干的)内存类型的比较的外行人风格的答案。
记忆就是记忆。但是不同的东西可以访问该内存。 GPU 可以访问内存,CPU 可以访问内存,也许其他硬件位,无论如何。
如果其他人对该内存所做的更改对 reader 可见,则该特定事物具有对内存的“一致”访问。现在,你可能认为这是愚蠢的。毕竟,如果内存被更改了,怎么可能有人可能看不到?
简单地说,缓存。
原来换内存的代价很大。因此,我们尽一切可能 避免 更改内存,除非我们绝对必须这样做。当您将 CPU 中的单个字节写入内存中的指针时,CPU 尚未写入该字节。或者至少,不是为了记忆。它将它写入该内存的本地副本,称为“缓存”。
这是因为,一般来说,应用程序不会写入(或读取)单个字节。他们更有可能以小块的形式写入(和读取)大量字节。因此,如果您要执行诸如内存加载或存储之类的昂贵操作,则应该加载或存储大块内存。因此,您将要对缓存中的内存块进行的所有更改存储起来,然后在将来的某个时间将该缓存块写入实际内存。
但是,如果您有两个使用相同内存的独立设备,则需要一些方法来确保一个设备所做的写入对其他设备可见。大多数 GPU 无法读取 CPU 缓存。大多数 CPU 语言都没有语言级别的支持,无法说“嘿,我写到内存中的那些东西?我的意思是让你现在就把它写到内存中。”因此,您通常需要一些东西来确保更改的可见性。
在 Vulkan 中,由 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
标记的内存意味着,如果您 read/write 该内存(通过映射指针,因为这是 Vulkan 允许您直接写入内存的唯一方式),您 不需要 使用函数 vkInvalidateMappedMemoryRanges
/vkFlushMappedMemoryRanges
来确保 CPU/GPU 可以看到这些更改。任何更改的可见性在两个方向上都得到保证。如果该标志在内存中不可用,则必须使用上述函数来确保要访问的特定数据区域的一致性。
对于相干内存,硬件方面的两件事之一正在发生。 CPU 对内存的访问未缓存在任何 CPU 的缓存中,或者 GPU 可以直接访问 CPU 的缓存(可能是因为在同一个作为 CPU(s)) 死去。您通常可以看出后者正在发生,因为 Vulkan 的片上 GPU 实现不会费心提供非一致性内存选项。
如果内存是一致的,那么访问该内存的所有线程必须始终就内存状态达成一致,例如:如果线程 0 读取内存位置 A 而线程 1 同时读取同一位置,则两个线程应始终读取相同的值。
但是如果内存不一致,那么线程 A 和 B 可能会读回不同的值。线程 0 可能认为位置 A 包含一个 1,而线程认为该位置包含一个 2。不同的线程对内存的看法可能不一致。
大量内核很难实现连贯性。通常每个核心都必须知道来自所有其他核心的内存访问。因此,如果您在四核中有 4 个内核 CPU,则一致性并不难实现,因为每个内核都必须了解其他 3 个内核的内存访问地址,但在具有 16 个内核的 GPU 中,每个内核都必须了解其他 15 个内核的内存访问。核心使用所谓的 "cache coherence protocols" 交换有关其缓存内容的数据。
这就是为什么 GPU 通常只支持有限形式的一致性。如果某些内存位置是只读的或仅由单个线程访问,则不需要一致性。如果高速缓存很小并且并不总是需要一致性,而只是在程序的特定指令处才需要,那么可以在特定内存访问之前或之后使用高速缓存刷新来实现程序的正确行为。
如果您的硬件同时提供连贯和非连贯内存类型,那么您可以预期非连贯内存会更快,但如果您尝试 运行 使用此内存的并行算法,它们将失败真的很奇怪的方式。