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