GPU上的相干内存是什么?

24
我曾经在与图形编程相关的技术论文中多次看到“不一致”的和“一致”的内存术语。我一直在寻找一个简单明了的解释,但大多数都是类似于这篇this 难度较高的论文。如果能够得到通俗易懂的回答来解释GPU架构中的一致内存实际上是什么,以及如何与其他(可能是非一致)内存类型进行比较,我将不胜感激。tech papers

2
“一致性”意味着如果一个数据对象被多个代理(或通过多个路径)访问,每个代理将看到完全相同的状态。这两个代理可以是CPU和GPU。例如,两个读取路径可能是通过纹理缓存与L1缓存。保持一致性通常需要额外的硬件机制,例如用于跟踪缓存行的MESI或MOESI状态的位,并且可能会导致大量的一致性流量来传输数据,特别是如果有许多代理。 - njuffa
1
GPU中的纹理缓存是“不一致”机制的典型例子。如果纹理映射下的数据发生变化,纹理缓存中的任何缓存内容可能无法失效或刷新,并且对纹理缓存的后续访问会导致读取过时数据。 - njuffa
2个回答

36

内存就是内存。但是不同的设备可以访问这个内存。GPU 可以访问内存,CPU 可以访问内存,可能还有其他硬件部件。

如果其他人对内存所做的更改对读取者是可见的,那么特定的设备对内存具有“一致性”访问。现在,您可能认为这是愚蠢的。毕竟,如果内存已经被更改,某人怎么会不可能看到呢?

简而言之,就是缓存。

事实证明,更改内存是昂贵的。因此,我们尽一切可能避免更改内存,除非我们绝对必须。当您从 CPU 向内存中的指针写入单个字节时,CPU 并不会立即将该字节写入内存。或者至少不是写入内存。它将其写入内存的本地副本,称为“缓存”。

这样做的原因是,一般来说,应用程序不会写入(或读取)单个字节。它们更有可能以小块的方式写入(和读取)大量字节。因此,如果您要执行像内存加载或存储这样的代价高昂的操作,您应该加载或存储大块的内存。因此,您将要对一块内存所做的所有更改存储在缓存中,然后在将来某个时间点上将该缓存块的所有更改作为单个写操作写入实际内存。

但是,如果您有两个不同的设备使用相同的内存,您需要一些方法确保一个设备所做的写入对其他设备是可见的。大多数 GPU 无法读取 CPU 缓存。而且大多数 CPU 语言没有语言级别的支持来表示“嘿,我写入到内存的那些内容?我真正意思是让你现在把它写入内存。” 因此,您通常需要一些东西来确保更改的可见性。

在 Vulkan 中,被标记为 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT 的内存意味着,如果你读/写该内存(通过映射指针,因为这是 Vulkan 允许你直接写入内存的唯一方式),你不需要使用 vkInvalidateMappedMemoryRanges/vkFlushMappedMemoryRanges 函数来确保 CPU/GPU 可以看到这些更改。任何更改的可见性都是双向保证的。如果该标志不可用于内存,则必须使用上述函数来确保你想要访问的特定数据区域的协同性。
使用一致的内存,其硬件方面有两种情况。要么 CPU 对内存的访问未缓存在任何 CPU 缓存中,要么 GPU 直接访问 CPU 缓存(可能由于与 CPU(s)在同一晶片上)。通常可以通过观察是否存在非一致内存选项来判断后一种情况是否发生,因为 Vulkan 的基于芯片内 GPU 实现不会提供非一致内存选项。

2
"VK_MEMORY_PROPERTY_HOST_COHERENT_BIT 位指定主机缓存管理命令 vkFlushMappedMemoryRanges 和 vkInvalidateMappedMemoryRanges 不需要刷新主机写入到设备或使设备写入对主机可见。" - Vulkan 规范。因此,它显然可以在两个方向上工作。 - Daniel Russell
@DanielRussell 我编辑了答案,加入了这一点,因为答案给人的印象是主机一致性只影响CPU->GPU写入,而不是反过来。 - plasmacel

8
如果内存是一致的,那么所有访问该内存的线程都必须始终同意内存的状态,例如:如果线程0读取内存位置A并且线程1在同一时间读取相同位置,则两个线程应始终读取相同值。
但是,如果内存不一致,则线程A和B可能会读取不同的值。线程0可能认为位置A包含1,而线程1则认为该位置包含2。不同的线程将对内存具有不一致的视图。
在高核心数量下实现一致性很难。通常每个核心都必须知道所有其他核心的内存访问情况。因此,如果您在四核心CPU中有4个核心,则实现一致性并不难,因为每个核心必须了解3个其他核心的内存访问地址,但是在具有16个核心的GPU中,每个核心必须知道15个其他核心的内存访问情况。核心使用所谓的“缓存一致性协议”交换其缓存内容的数据。
这就是为什么GPU通常只支持有限形式的一致性。如果某些内存位置是只读的或仅由单个线程访问,则不需要一致性。如果缓存很小,并且不总是需要一致性,而仅在程序的特定指令时需要,则可以在特定内存访问之前或之后使用缓存刷新来实现程序的正确行为。
如果您的硬件提供一致性和非一致性内存类型,则可以预期非一致性内存将更快,但是如果尝试使用此内存运行并行算法,则它们将以非常奇怪的方式失败。

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