此问题在此处已有答案:
Does malloc lazily create the backing pages for an allocation on Linux (and other platforms)?(6个答案)
Can you manually allocate virtual pages in Linux?(2个答案)
Does mmap or malloc allocate RAM?(2个答案)
How to allocate "huge" pages for C++ application on Linux(3个答案)
两个月前关门了。
我有一个用gcc 11.2编译的程序,它首先在heap(使用new)上分配一些RAM内存(8 GB),然后用从示波器实时读取的数据填充它。
uint32_t* buffer = new uint32_t[0x80000000];
for(uint64_t i = 0; i < 0x80000000; ++i) buffer[i] = GetValueFromOscilloscope();
我所面临的问题是优化器跳过了第一行的分配,并在我遍历循环时动态地完成它。这降低了循环每次迭代所花费的时间。因为在循环中尽可能高效是很重要的,所以我找到了一种方法来强制编译器在进入for循环之前分配内存,也就是将所有保留值设置为零:
uint32_t* buffer = new uint32_t[0x80000000]();
我的问题是:有没有一种不需要强制数据为零就能达到同样效果的方法(除了关闭优化标志)?我只想强制编译器在声明时保留内存,但我不在乎保留的值是否为零。
提前感谢!
EDIT 1:我看到的优化器延迟分配的证据是,当我遍历循环时,'gnome-system-monitor'显示RAM内存缓慢增长,并且只有在我完成循环后,它才达到8 GiB。然而,如果我将所有值初始化为零,它gnome-system-monitor显示RAM内存快速增长到8 GiB,然后它开始循环。
编辑2:我正在使用Ubuntu 22.04.1 LTS
2条答案
按热度按时间im9ewurl1#
它与优化器没有什么关系,这里没有什么特别的事情发生,你的程序不会跳过任何一行,它会完全按照你的要求去做。
问题是,当你分配内存时,你同时与分配器和操作系统的分页系统连接。很可能,你的操作系统并没有让所有的分页都驻留在内存中,而是让一些分页被你的程序标记为已分配,并且只有当你真正使用它时,才会让这些内存真正存在。这是大多数操作系统的工作方式。
要解决这个问题,你需要与系统的虚拟内存分配器连接,使页面常驻。在Linux上,还有hugepage可以帮助你。在Windows上,有VirtualAlloc api,但我还没有深入研究那个平台。
xnifntxz2#
您似乎误解了这种情况。用户空间进程中的虚拟内存(在本例中为堆空间)确实会“立即”分配(可能是在几个系统调用协商更大的堆之后)。
然而,每一个页面对齐的、页面大小的虚拟内存块,如果你还没有接触到,最初将缺少物理页面支持。虚拟页面被惰性地Map到物理页面,(只有)当需要的时候。
也就是说,您所观察到的“分配”(作为对大堆空间的第一次访问的一部分)发生在GCC可以直接影响的抽象层之下的几个抽象层上,并由操作系统的分页机制处理。
旁注:另一个后果是,例如,在一台RAM为128 GB的机器上分配1 TB的虚拟内存块看起来工作得很好,只要你从来没有访问过大部分的巨大(惰性)分配空间。(如果需要,有一些配置选项可以限制这种 * 内存过量使用 *。)
当你第一次接触你新分配的虚拟内存页面时,每个页面都会导致页面错误,你的CPU会因此而在内核的一个处理程序中结束。内核会评估这种情况,并确定访问实际上是合法的。因此,它“实体化”虚拟内存页面,即挑选物理页面来支持虚拟页面并更新其簿记数据结构 * 和 * 两者(同样重要的是)硬件页面Map机制(秒)(例如,页表或TLB,取决于体系结构)。然后,内核切换回用户空间进程,这将不会有任何线索,这一切刚刚发生。重复每一页。
可能上面的描述过于简单了。(例如,可以有多个页面大小,以在Map维护效率和粒度/碎片等之间取得平衡。)
确保内存缓冲区得到硬件支持的一个简单而丑陋的方法是找到架构上可能的最小页面大小(例如,在x86_64上为4 kiB,因此是这些整数的1024(在大多数情况下)),然后预先访问该内存的每个(可能的)页面,如下所示:
for (size_t i = 0; i < 0x80000000; i += 1024) buffer[i] = 1;
.(当然)还有比这更合理的解决办法↑;这只是一个例子来说明发生了什么以及原因。