对于运行 Linux 5.4.0-51-generic 的标准 Ubuntu 20.04 机器...
我们可以直接观察这一点。在下面的代码中,我增加了n
价值1 << 24
(约 1600 万int
32 位 s = 64MBint
)所以它是总体内存使用的主导因素。我编译、运行并观察了内存使用情况htop
:
#include <unistd.h>
int main() {
int *foo = new int [1 << 24];
sleep(100);
}
htop 值:VIRT 71416KB / RES 1468KB
虚拟地址分配包括分配的内存new
,但驻留内存大小要小得多 - 表明分配的所有 64MB 还不需要不同的物理后备内存页面。
更改为后int *foo = new int[1<<24]();
:
htop 值:VIRT 71416KB / RES 57800KB
请求内存清零会导致驻留内存值略低于初始化的 64MB,这并不是由于内存压力(我有 64GB RAM),但内核中的某些算法必须决定分页出一些内存归零后的后备存储器(我怀疑kswapd
?)。大 RES 值表明每个归零页都被赋予了物理后备内存的不同页(与例如映射到操作系统的零页以进行实际后备页的 COW 分配不同)。
与结构:
#include <unistd.h>
struct X {
int a[1 << 24];
};
int main() {
auto foo = new X;
sleep(100);
}
htop 值:VIRT 71416KB / RES 1460KB
这表明静态数组的 RES 不足以具有不同的支持页面。虚拟内存要么已预先映射到操作系统零页,要么未映射,并且在访问时最初会映射到零页,然后在写入时给出其自己的物理支持页 - 我不确定是哪一个,但就实际物理 RAM 使用情况而言,它没有任何区别。
更改为后auto foo = new X{};
htop 值:VIRT 71416KB / RES 67844KB
您可以清楚地看到,将字节初始化为 0 导致使用数组的后备内存。
解决您的问题:
Linux内核会使用惰性内存分配吗?
虚拟内存分配完成时new
已经完成了。当用户空间代码对内存进行实际写入时,会延迟分配不同的物理后备内存。
对于第二种情况,与创建静态数组时的方式相同吗?
#include <unistd.h>
int g_a[1 << 24];
int f(int i) {
static int a[1 << 24];
return a[i];
}
int main(int argc, const char* argv[]) {
sleep(20);
int k = f(2930);
sleep(20);
return argc + k;
}
VIRT 133MB分辨率 1596KB
运行时,内存didn't20秒后跳转,说明程序加载时虚拟地址空间全部分配完毕。低常驻内存表明页面已not按原来的方式访问和归零new
.
只是为了解决一个潜在的混乱点:虽然 Linux 内核会在第一次向进程提供后备内存时将其清零,但任何给定的调用new
(在我见过的任何实现中)不会知道分配的内存是否是从早期的动态分配中回收的 - 可能已写入非零值 - 从那以后delete
d/free
d.因此,如果您使用内存清零形式,例如new X{}
or new int[n]()
那么内存将被用户空间代码无条件地清除,导致全部的后备内存被分配并出现故障。