映射 GPU 内存时应该使用 volatile 吗?
Should volatile be used when mapping GPU memory?
OpenGL 和 Vulkan 都允许分别使用 glMapBuffer
和 vkMapMemory
来获取指向部分 GPU 内存的指针。他们都给映射内存一个 void*
。要将其内容解释为某些数据,必须将其转换为适当的类型。最简单的示例可能是转换为 float*
以将内存解释为浮点数组或向量数组或类似数组。
似乎 C++ 中的任何一种内存映射 ,因为它没有内存映射的概念。但是,这并不是真正的问题,因为该主题超出了 C++ 标准的范围。但是,还有volatile
.
的问题
在链接的问题中,指针被额外标记为 volatile
因为它指向的内存内容可以以编译器在编译过程中无法预料的方式进行修改。这似乎是合理的,尽管我很少看到人们在这种情况下使用 volatile
(更广泛地说,现在似乎几乎没有使用这个关键字)。
同时在 中,答案似乎是不需要使用 volatile
。这是因为他们所说的内存是使用 mmap
映射的,后来又被赋予 msync
,这可以被视为修改内存,这类似于在 Vulkan 或 OpenGL 中显式刷新它。恐怕这不适用于 OpenGL 和 Vulkan。
如果内存被映射为非 GL_MAP_FLUSH_EXPLICIT_BIT
或 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
,则根本不需要刷新,内存内容会自动更新。即使使用 vkFlushMappedMemoryRanges
或 glFlushMappedBufferRange
手动刷新内存,这些函数实际上都没有将映射指针作为参数,因此编译器不可能知道它们修改了映射内存的内容。
因此,是否有必要将指向映射 GPU 内存的指针标记为 volatile
?我知道从技术上讲这都是未定义的行为,但我想问的是在实际硬件上实际需要什么。
顺便说一下,Vulkan Specification or the OpenGL Specification 根本没有提到 volatile
限定词。
编辑:将内存标记为 volatile
会导致性能开销吗?
好的,假设我们有一个编译器,它对代码中发生的一切都了如指掌。这意味着编译器可以跟踪任何指针,即使通过 运行时执行 你的代码每次都完美和正确,无论你如何试图隐藏它。因此,即使您在程序的一端读取了一个字节,编译器也会以某种方式记住您读取的确切字节,并且在您尝试再次读取它们时,它可以选择不执行该读取,只给您之前的值,除非编译器知道可以改变它的东西。
但是我们还要说我们无所不知的编译器完全没有注意到 OpenGL/Vulkan 中发生的一切。对于这个编译器,图形 API 是一个黑盒子。这里有龙
所以你从 API 得到一个指针,从中读取,GPU 写入它,然后你想读取 GPU 刚刚写入的新数据。为什么编译器会认为该指针背后的数据已被更改?毕竟,更改来自系统外部,来自 C++ 标准无法识别的来源。
这就是 volatile
的用途,对吧?
嗯,事情是这样的。在 OpenGL 和 Vulkan 中,要确保您可以实际读取该数据,您需要做一些事情。即使连贯地映射内存,也必须进行 API 调用以确保写入内存的 GPU 进程已实际执行。对于 Vulkan,您正在等待栅栏或事件。对于 OpenGL,您正在等待栅栏或执行完整的完成。
无论哪种方式,在执行从内存读取之前,无所不知的编译器都会遇到对黑盒的函数调用,正如之前建立的那样,编译器一无所知。由于映射指针本身来自同一个黑盒子,编译器不能假设黑盒子没有指向该内存的指针。因此,就编译器而言,调用这些函数 可能 将数据写入该内存。
因此,我们无所不知但又不经意的编译器不能优化掉这样的内存访问。一旦我们从这些函数中取回控制权,编译器必须假定通过该地址可到达的任何指针的任何内存都可能已被更改。
如果编译器能够查看图形 API 本身,阅读并理解这些函数在做什么,那么它肯定会看到会告诉它的东西,“哦,我不应该对通过这些指针检索的内存状态做出假设。"
这就是为什么您不需要 volatile
。
另请注意,这同样适用于写入数据。如果您写入持久的、连贯的映射内存,您仍然必须与图形 API 执行一些同步操作,以便您的 CPU 写入,以便 GPU 不读取它。所以这就是编译器知道它不能再依赖于它对以前写入数据的了解的地方。
OpenGL 和 Vulkan 都允许分别使用 glMapBuffer
和 vkMapMemory
来获取指向部分 GPU 内存的指针。他们都给映射内存一个 void*
。要将其内容解释为某些数据,必须将其转换为适当的类型。最简单的示例可能是转换为 float*
以将内存解释为浮点数组或向量数组或类似数组。
似乎 C++ 中的任何一种内存映射 volatile
.
在链接的问题中,指针被额外标记为 volatile
因为它指向的内存内容可以以编译器在编译过程中无法预料的方式进行修改。这似乎是合理的,尽管我很少看到人们在这种情况下使用 volatile
(更广泛地说,现在似乎几乎没有使用这个关键字)。
同时在 volatile
。这是因为他们所说的内存是使用 mmap
映射的,后来又被赋予 msync
,这可以被视为修改内存,这类似于在 Vulkan 或 OpenGL 中显式刷新它。恐怕这不适用于 OpenGL 和 Vulkan。
如果内存被映射为非 GL_MAP_FLUSH_EXPLICIT_BIT
或 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
,则根本不需要刷新,内存内容会自动更新。即使使用 vkFlushMappedMemoryRanges
或 glFlushMappedBufferRange
手动刷新内存,这些函数实际上都没有将映射指针作为参数,因此编译器不可能知道它们修改了映射内存的内容。
因此,是否有必要将指向映射 GPU 内存的指针标记为 volatile
?我知道从技术上讲这都是未定义的行为,但我想问的是在实际硬件上实际需要什么。
顺便说一下,Vulkan Specification or the OpenGL Specification 根本没有提到 volatile
限定词。
编辑:将内存标记为 volatile
会导致性能开销吗?
好的,假设我们有一个编译器,它对代码中发生的一切都了如指掌。这意味着编译器可以跟踪任何指针,即使通过 运行时执行 你的代码每次都完美和正确,无论你如何试图隐藏它。因此,即使您在程序的一端读取了一个字节,编译器也会以某种方式记住您读取的确切字节,并且在您尝试再次读取它们时,它可以选择不执行该读取,只给您之前的值,除非编译器知道可以改变它的东西。
但是我们还要说我们无所不知的编译器完全没有注意到 OpenGL/Vulkan 中发生的一切。对于这个编译器,图形 API 是一个黑盒子。这里有龙
所以你从 API 得到一个指针,从中读取,GPU 写入它,然后你想读取 GPU 刚刚写入的新数据。为什么编译器会认为该指针背后的数据已被更改?毕竟,更改来自系统外部,来自 C++ 标准无法识别的来源。
这就是 volatile
的用途,对吧?
嗯,事情是这样的。在 OpenGL 和 Vulkan 中,要确保您可以实际读取该数据,您需要做一些事情。即使连贯地映射内存,也必须进行 API 调用以确保写入内存的 GPU 进程已实际执行。对于 Vulkan,您正在等待栅栏或事件。对于 OpenGL,您正在等待栅栏或执行完整的完成。
无论哪种方式,在执行从内存读取之前,无所不知的编译器都会遇到对黑盒的函数调用,正如之前建立的那样,编译器一无所知。由于映射指针本身来自同一个黑盒子,编译器不能假设黑盒子没有指向该内存的指针。因此,就编译器而言,调用这些函数 可能 将数据写入该内存。
因此,我们无所不知但又不经意的编译器不能优化掉这样的内存访问。一旦我们从这些函数中取回控制权,编译器必须假定通过该地址可到达的任何指针的任何内存都可能已被更改。
如果编译器能够查看图形 API 本身,阅读并理解这些函数在做什么,那么它肯定会看到会告诉它的东西,“哦,我不应该对通过这些指针检索的内存状态做出假设。"
这就是为什么您不需要 volatile
。
另请注意,这同样适用于写入数据。如果您写入持久的、连贯的映射内存,您仍然必须与图形 API 执行一些同步操作,以便您的 CPU 写入,以便 GPU 不读取它。所以这就是编译器知道它不能再依赖于它对以前写入数据的了解的地方。