12

我在 Linux 中编写了一个 malloc 内存的 C 程序,循环运行它,TOP 没有显示任何内存消耗。

然后我用那个内存做了一些事情,并且 TOP 确实显示了内存消耗。

当我 malloc 时,我是否真的“获取内存”,或者是否存在“惰性”内存管理,如果/当我使用它时,它只会给我内存?

(还有一个选项是TOP在我使用的时候才知道内存消耗,所以我不确定这个..)

谢谢

4

6 回答 6

23

在 Linux 上,malloc 使用 sbrk() 或 mmap() 请求内存 - 无论哪种方式,您的地址空间都会立即扩展,但 Linux 在第一次写入相关页面之前不会分配实际的物理内存页面。您可以在 VIRT 列中看到地址空间扩展,而在 RES 中看到实际的物理内存使用情况。

于 2009-05-14T16:40:36.780 回答
6

这开始有点偏离主题(然后我会将它与您的问题联系起来),但是发生的事情类似于您在 Linux 中分叉一个进程时发生的事情。分叉时有一种称为写时复制的机制,它只在内存也被写入时为新进程复制内存空间。这样,如果分叉的进程 exec 立即成为一个新程序,那么您就节省了复制原始程序内存的开销。

回到你的问题,这个想法是相似的。正如其他人指出的那样,请求内存会立即为您提供虚拟内存空间,但实际页面仅在写入时才分配。

这样做的目的是什么?它基本上使分配内存成为或多或少的恒定时间操作 Big O(1) 而不是 Big O(n) 操作(类似于 linux 调度程序传播它的方式而不是在一个大块中执行它)。

为了证明我的意思,我做了以下实验:

rbarnes@rbarnes-desktop:~/test_code$ time ./bigmalloc 

real    0m0.005s
user    0m0.000s
sys 0m0.004s
rbarnes@rbarnes-desktop:~/test_code$ time ./deadbeef 

real    0m0.558s
user    0m0.000s
sys 0m0.492s
rbarnes@rbarnes-desktop:~/test_code$ time ./justwrites 

real    0m0.006s
user    0m0.000s
sys 0m0.008s

bigmalloc 程序分配了 2000 万个整数,但对它们不做任何事情。deadbeef 将一个 int 写入每个页面,导致 19531 次写入,而 justwrites 分配 19531 个 int 并将它们清零。如您所见, deadbeef 的执行时间比 bigmalloc 长约 100 倍,比 justwrites 长约 50 倍。

#include <stdlib.h>    

int main(int argc, char **argv) {

int *big = malloc(sizeof(int)*20000000); // allocate 80 million bytes

return 0;

}

.

#include <stdlib.h>    

int main(int argc, char **argv) {

int *big = malloc(sizeof(int)*20000000); // allocate 80 million bytes

// immediately write to each page to simulate all at once allocation

// assuming 4k page size on 32bit machine

for ( int* end = big + 20000000; big < end; big+=1024 ) *big = 0xDEADBEEF ;    

return 0;

}

.

#include <stdlib.h>

int main(int argc, char **argv) {

int *big = calloc(sizeof(int),19531); // number of writes

return 0;
}
于 2009-05-14T18:46:38.020 回答
3

是的,除非您触摸它,否则内存不会映射到您的内存空间。mallocing 内存只会设置分页表,因此他们知道当您在分配的内存中遇到页面错误时,应该映射内存。

于 2009-05-14T16:41:06.917 回答
2

你在使用编译器优化吗?也许优化器已经删除了分配,因为您没有使用分配的资源?

于 2009-05-14T16:41:19.227 回答
1

该功能称为过度使用- 内核通过增加数据段大小来“承诺”您的内存,但不会为其分配物理内存。当您触摸该新空间中的地址时,进程页面错误会进入内核,然后内核会尝试将物理页面映射到该地址。

于 2009-05-14T21:23:16.990 回答
1

是的,请注意VirtualAlloc标志,

MEM_RESERVE
MEM_COMMIT

.

嘿,但是对于Linux或任何POSIX/BSD/SVR#系统,vfork() 已经存在了很长时间并提供了类似的功能。

vfork() 函数与 fork() 的区别仅在于子进程可以与调用进程(父进程)共享代码和数据。如果 vfork() 被滥用,这会显着加速克隆活动,但会危及父进程的完整性。

不建议将 vfork() 用于任何目的,除非作为立即调用 exec 系列函数或 _exit() 的前奏。

vfork() 函数可用于创建新进程,而无需完全复制旧进程的地址空间。如果分叉的进程只是要调用 exec,则不会使用通过 fork() 从父进程复制到子进程的数据空间。这在分页环境中特别低效,因此 vfork() 特别有用。根据父数据空间的大小,vfork() 可以比 fork() 显着提高性能。

于 2009-07-14T06:50:12.150 回答