我有一个用GCC 11.2编译的程序,它首先在堆上分配一些内存(8 GB)(使用new),然后用从示波器实时读出的数据填充它.

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]();

我的问题是:有没有一种侵入性更小的方法来达到同样的效果,而不是从一开始就强制数据为零(除了关闭优化标志)?我只想强制编译器在声明时保留内存,但我不在乎保留的值是否为零.

提前谢谢!

EDIT1:我看到的知道优化器延迟分配的证据是,当我遍历循环时,‘gnome-system-monitor’显示RAM内存缓慢增长,并且只有在我完成循环后,它才达到8GiB.然而,如果我将所有值初始化为零,则GNOME-SYSTEM-MONITOR显示快速增长到8GiB,然后它开始循环.

EDIT2:我正在使用Ubuntu 22.04.1 LTS

推荐答案

你似乎曲解了情况.用户空间进程(在本例中为堆空间)内的虚拟内存确实会"立即"分配(可能在协商更大堆的几个系统调用之后).

但是,您还没有接触到的每个页面对齐的页面大小的虚拟内存块最初将缺乏物理页面支持.虚拟页面延迟映射到物理页面,(仅在需要时).

也就是说,您观察到的"分配"(作为第一次访问大堆空间的一部分)发生在GCC可以直接影响并由操作系统的分页机制处理的抽象层之下.

附注:另一个后果是,例如,在一台内存为128 GB的机器上分配1 TB的虚拟内存块似乎工作得非常好,只要您永远不会访问所分配的大部分巨大(懒惰)空间.(如果需要,有一些配置选项可以限制这样的memory overcommitment个.)

当您第一次接触新分配的虚拟内存页面时,每个页面都会导致页面错误,因此您的CPU最终会进入内核中的处理程序.内核判断情况并确定访问实际上是合法的.因此,它将虚拟存储器页面具体化,即挑选物理页面来支持虚拟页面,并更新其两个簿记数据 struct and(同样重要的是)硬件页面映射机制(例如,页表或TLB,取决于体系 struct ).然后,内核切换回您的用户空间进程,它不会知道这一切是否刚刚发生.对每一页重复上述步骤.

想必,上述描述过于简单化了.(例如,可以有多种页面大小,以在映射维护效率和粒度/碎片等之间取得平衡.)

确保内存缓冲区获得硬件支持的一种简单而难看的方法是在您的体系 struct 上找到可能的最小页面大小(例如,在x86_64上是4 KiB,因此1024个整数(在大多数情况下是这样的)),然后预先访问该内存的每个(可能的)页面,如:for (size_t i = 0; i < 0x80000000; i += 1024) buffer[i] = 1;.

当然,还有比↑更合理的解决方案;这只是一个例子,说明正在发生的事情和原因.

Linux相关问答推荐

是否有例外情况需要在.gitconfig中使用?

X86_64程序集中的分段故障:系统调用问题

无法分析nasm中的单词

在Linux中随机化txt文件但保证不重复行

为什么库中不调用全局变量的构造函数?

在 Bash 中使用 shell 脚本从文件中解析版本号

/proc/mounts 没有像 /proc/self/mountinfo 这样的源信息

如何在不进行轮询且不吃掉其他人子进程的退出代码的情况下等待一组子进程(并且只有它们)?

输出特定字符的所有列号

如何更改文件的上次访问/修改/更改日期?

使用 awk 从文件中检索一组特定的字符串

BASEDIR 环境变量未正确定义

如何使用 AWK 合并两个文件?

如何有效地使用 grep?

使用正则表达式时,Shell 'tar: not found in archive' 错误

发出信号并中断比较

如何以编程方式禁用硬件预取?

Anaconda:禁用提示更改

如何使用cp从不同目录复制多个文件?

如何在我的终端中编辑文本文件