当默认值为 0 时,为什么 vec![0, <super large number>] 如此节省内存?

Aen*_*don 6 rust

我正在处理 Rust 中的大量数据,在评估数据结构的内存使用情况时,我偶然发现了一些令人惊讶的结果。

首先,我尝试通过将零推入其中来手动填充向量:

let mut arr = vec![];

for _ in 0..(4_294_967_294 as u32) {
    arr.push(0);
}
Run Code Online (Sandbox Code Playgroud)

一段时间后,不出所料,我会说,我的计算机耗尽了可用内存,并且该进程被操作系统终止了。

但是,如果我使用宏初始化来初始化向量,行为会发生变化:

let mut arr = vec![0; 2_147_483_647_000];

for i in 1..1_000_000_000 {
    arr[i-1] = rng.next_u64();

    let sample = rng.next_u32();
    let res = arr[sample as usize];
    if i % 10000000 == 0 {
        print!("After {} ", i);
        print!("Btw, arr[{}] == {} ", sample, res);
        print_allocated_memory();
    }
}
Run Code Online (Sandbox Code Playgroud)

尽管我用实际的 u64 值填充了 10 亿个条目,并从数组中读出了随机值(大部分是零,我只是试图在此处排除整个数组的编译器优化),但我的计算机内存并没有溢出。

内存使用情况jemalloc如下(请注意,我的计算机仅安装了 16 GB RAM):

allocated 16777216.05 MiB resident 16777223.02 MiB
Run Code Online (Sandbox Code Playgroud)

...而我的操作系统在代码末尾报告了最大大约 8000M(以 htop 测量)。

奇怪的是,如果我使用 0 以外的任何其他默认值(无论是 1 还是 100),宏在完成向量创建之前就会耗尽内存,因此它肯定与 init 值为 0 有关。

我想知道宏如何保持结果数据结构的内存效率?数组中的元素不是真的被创建了吗?如果不是,那么它如何与我一起从向量中读出随机索引呢?

我已经检查过文档,尽管它只说它依赖于 type 的默认元素Clone,这对于原始类型来说并没有任何意义。

cdh*_*wie 9

为向量分配内存时,可以使用一些分配内置函数。当向量的类型为数字且给定的初始值为零时,__rust_alloc_zeroed使用 。

在 Unix 兼容系统上,此分配器函数的默认实现可以使用calloc()posix_memalign()

calloc()保证分配归零;posix_memalign()才不是。如果使用后者,Rust 分配器会将内存本身清零。

鉴于您观察到的行为,唯一合理的解释是calloc()使用了该行为。由于使用先前释放的内存的库无法满足该请求(分配肯定太大),因此该请求被传递到内核,内核在进程的页表中为请求的分配创建条目。

但是,操作系统不必实际为分配中的每个页面分配物理内存区域。它可以将其推迟到以后,一种称为过度承诺的技术。

如果尚未得到物理内存的支持,则读取或写入分配区域中的地址将触发页面错误。当发生此错误时,内核通过将内存区域分配给访问的页面来解决它。

所有这一切的最终结果是,如果您创建一个初始值为零的数字类型向量,则该分配最初实际使用的系统内存非常少。几乎所有分配都在还没有支持系统内存的页面内,类似于稀疏文件中的漏洞。当您写入向量时,系统将开始为分配分配物理内存,并且您使用的内存(和/或使用的交换)将开始增加。