fer*_*ury 9 c++ linux gcc memory-management heap-memory
我有一个用 gcc 11.2 编译的程序,它首先在堆上分配一些 RAM 内存(8 GB)(使用 new),然后用从示波器实时读出的数据填充它。
\nuint32_t* buffer = new uint32_t[0x80000000];\nfor(uint64_t i = 0; i < 0x80000000; ++i) buffer[i] = GetValueFromOscilloscope();\nRun Code Online (Sandbox Code Playgroud)\n我面临的问题是优化器跳过第一行的分配,并在我遍历循环时动态执行分配。这会减慢循环每次迭代所花费的时间。因为在循环期间尽可能高效很重要,所以我找到了一种方法来强制编译器在进入 for 循环之前分配内存,即将所有保留值设置为零:
\nuint32_t* buffer = new uint32_t[0x80000000]();\nRun Code Online (Sandbox Code Playgroud)\n我的问题是: \xc2\xbfi 是否有一种侵入性较小的方法可以实现相同的效果,而无需首先强制数据为零(除了关闭优化标志之外)?我只是想强制编译器在声明时保留内存,但我不关心保留值是否为零。
\n提前致谢!
\nEDIT1:我看到的知道优化器延迟分配的证据是,当我遍历循环时,“gnome-system-monitor”显示 RAM 内存缓慢增长,只有在完成循环后,它才达到 8 GiB。然而,如果我将所有值初始化为零,则 gnome-system-monitor 显示快速增长至 8 GiB,然后开始循环。
\nEDIT2:我正在使用 Ubuntu 22.04.1 LTS
\nGui*_*cot 16
和优化器关系不大。这里没有发生什么引人注目的事情。您的程序不会跳过任何行,并且它完全按照您的要求执行。
问题是,当您分配内存时,您正在与分配器和操作系统的分页系统进行交互。最有可能的是,您的操作系统并没有将所有这些页面驻留在内存中,而是将一些页面标记为由您的程序分配,并且只会在您实际使用该内存时才使其实际存在。这就是大多数操作系统的工作方式。
要解决此问题,您需要与系统的虚拟内存分配器交互以使页面驻留。在 Linux 上,还有一个巨大的页面可以帮助你。在 Windows 上,有VirtualAlloc api,但我还没有深入研究该平台。
And*_*mek 14
你似乎误解了情况。用户空间进程中的虚拟内存(在本例中为堆空间)确实会立即分配 \xe2\x80\x9c\xe2\x80\x9d (可能在几次协商较大堆的系统调用之后)。
\n但是,您尚未触及的每个页面对齐的页面大小的虚拟内存块最初将缺乏物理页面支持。(仅)在需要时才将虚拟页面延迟映射到物理页面。
\n也就是说,您正在观察的 \xe2\x80\x9callocation\xe2\x80\x9d (作为第一次访问大堆空间的一部分)正在 GCC 可以直接影响并由您处理的抽象层下面进行操作系统\xe2\x80\x99s的分页机制。
\n旁注:另一个结果是,例如,在具有 128 GB RAM 的计算机上分配 1 TB 虚拟内存块似乎工作得很好,只要您从不访问该巨大内存块的大部分(懒惰地) )分配的空间。(如果需要的话,有一些配置选项可以限制这种内存过量使用。)
\n当您第一次接触新分配的虚拟内存页面时,每个页面都会导致页面错误,因此您的 CPU 最终会进入内核中的处理程序。内核评估情况并确定访问实际上是合法的。因此,它\xe2\x80\x9c物质化\xe2\x80\x9d虚拟内存页面,即选择一个物理页面来支持虚拟页面并更新其簿记数据结构和(同样重要的)硬件页面映射机制(例如页表或 TLB,具体取决于体系结构)。然后内核切换回用户空间进程,它不会知道所有这一切刚刚发生。对每一页重复此操作。
\n据推测,上面的描述过于简单化了。(例如,可以有多种页面大小,以在映射维护效率和粒度/碎片等之间取得平衡)
\n确保内存缓冲区获得硬件支持的一种简单而丑陋的方法是在您的架构上找到尽可能小的页面大小(例如,在 x86_64 上为 4 kiB,因此这些整数中有 1024 个(好吧,在大多数情况下)情况)),然后预先触摸该内存的每个(可能的)页面,如:for (size_t i = 0; i < 0x80000000; i += 1024) buffer[i] = 1;。
(当然)还有比\xe2\x86\x91更合理的解决方案;这只是一个例子来说明\xe2\x80\x99 发生了什么以及为什么。
\n