下面是一个C程序来介绍这个问题。
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
int main(int argc, char *argv[]) {
if(argc != 2) {
printf("Provide a number to indicate the number of bytes (in Mega)\n");
exit(8);
}
int num = atoi(argv[1]);
size_t max = num * pow(2, 18);
printf("declared %ld ints\n", max);
int *a = malloc(max * sizeof(int));
while(1) {
for(size_t i = 0; i < max; i++) {
printf("%d",a[i]);
}
}
return 0;
}
程序做简单的事情。它从命令行读取一个数字,比如n,然后通过malloc请求nMB内存。
问题是,当我启动程序并在终端中输入free(在Linux中)时,结果是free所指示的已使用内存比所请求的内存小得多(如果你给给予一个大的n)。
这是我输入./a.out 1000
后free的输出
$ free -h
total used free shared buff/cache available
Mem: 12Gi 649Mi 11Gi 0.0Ki 320Mi 11Gi
Swap: 4.0Gi 0B 4.0Gi
以及pmap的更详细的输出
$ pmap 18414 -x
18414: ./a.out 1000
Address Kbytes RSS Dirty Mode Mapping
00005642164b8000 4 4 0 r---- a.out
00005642164b9000 4 4 0 r-x-- a.out
00005642164ba000 4 4 0 r---- a.out
00005642164bb000 4 4 4 r---- a.out
00005642164bc000 4 4 4 rw--- a.out
0000564218248000 132 4 4 rw--- [ anon ]
00007fa6d1b9a000 1024016 12 12 rw--- [ anon ]
00007fa71039e000 160 160 0 r---- libc.so.6
00007fa7103c6000 1620 852 0 r-x-- libc.so.6
00007fa71055b000 352 148 0 r---- libc.so.6
00007fa7105b3000 16 16 16 r---- libc.so.6
00007fa7105b7000 8 8 8 rw--- libc.so.6
00007fa7105b9000 52 20 20 rw--- [ anon ]
00007fa7105d0000 8 4 4 rw--- [ anon ]
00007fa7105d2000 8 8 0 r---- ld-linux-x86-64.so.2
00007fa7105d4000 168 168 0 r-x-- ld-linux-x86-64.so.2
00007fa7105fe000 44 44 0 r---- ld-linux-x86-64.so.2
00007fa71060a000 8 8 8 r---- ld-linux-x86-64.so.2
00007fa71060c000 8 8 8 rw--- ld-linux-x86-64.so.2
00007ffdc8b06000 136 12 12 rw--- [ stack ]
00007ffdc8b9b000 16 0 0 r---- [ anon ]
00007ffdc8b9f000 4 4 0 r-x-- [ anon ]
---------------- ------- ------- -------
total kB 1026776 1496 100
当我将for循环中的句子修改为a[i] = 1;
时,事情变得有趣起来。当我写入内存时,free和pmap告诉我实际上有1000MB用于物理内存。
为什么会这样?是不是从堆中“读”不会把新的页面带到物理内存中,而“写”会?我怀疑这与所谓的匿名文件有关。然而,关于它的讨论却很少。我在网上找不到有用的东西。
如果有人能给我给予一些帮助,我将不胜感激。
更新:对于那些好奇编译器是否进行优化的人,这里是汇编代码:
.file "memory-user.c"
.text
.section .rodata
.align 8
.LC0:
.string "Provide a number to indicate the number of bytes (in Mega)"
.LC3:
.string "declared %ld ints\n"
.LC4:
.string "%d"
.text
.globl main
.type main, @function
main:
.LFB6:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $48, %rsp
movl %edi, -36(%rbp)
movq %rsi, -48(%rbp)
cmpl $2, -36(%rbp)
je .L2
leaq .LC0(%rip), %rax
movq %rax, %rdi
call puts@PLT
movl $8, %edi
call exit@PLT
.L2:
movq -48(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rdi
call atoi@PLT
movl %eax, -28(%rbp)
pxor %xmm1, %xmm1
cvtsi2sdl -28(%rbp), %xmm1
movsd .LC1(%rip), %xmm0
mulsd %xmm1, %xmm0
comisd .LC2(%rip), %xmm0
jnb .L3
cvttsd2siq %xmm0, %rax
movq %rax, -16(%rbp)
jmp .L4
.L3:
movsd .LC2(%rip), %xmm1
subsd %xmm1, %xmm0
cvttsd2siq %xmm0, %rax
movq %rax, -16(%rbp)
movabsq $-9223372036854775808, %rax
xorq %rax, -16(%rbp)
.L4:
movq -16(%rbp), %rax
movq %rax, -16(%rbp)
call getpid@PLT
movl %eax, %edx
movq -16(%rbp), %rax
movq %rax, %rsi
leaq .LC3(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movq -16(%rbp), %rax
movl $4, %esi
movq %rax, %rdi
call calloc@PLT
movq %rax, -8(%rbp)
.L7:
movq $0, -24(%rbp)
jmp .L5
.L6:
movq -24(%rbp), %rax
leaq 0(,%rax,4), %rdx
movq -8(%rbp), %rax
addq %rdx, %rax
movl (%rax), %eax
movl %eax, %esi
leaq .LC4(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
addq $1, -24(%rbp)
.L5:
movq -24(%rbp), %rax
cmpq -16(%rbp), %rax
jb .L6
jmp .L7
.cfi_endproc
.LFE6:
.size main, .-main
.section .rodata
.align 8
.LC1:
.long 0
.long 1091567616
.align 8
.LC2:
.long 0
.long 1138753536
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
3条答案
按热度按时间rryofs0p1#
让我们首先指出,阅读
malloc
'的艾德内存而不首先对其进行初始化是未指定的行为。也许你应该使用calloc()
代替。在glibc(我假设您正在使用的库)中,
malloc()
(以及calloc()
和家族中的其他库)通常使用brk
系统调用来管理堆。但是,对于像您这样的非常大的分配,使用mmap
(如果您希望更改malloc()
开始使用mmap
的阈值,请参阅mallopt()
)。这两个系统调用都调用操作系统的虚拟内存管理器,它必须为您分配一些内存页(在x86上,常规页为4KiB)。但是,大多数操作系统都执行延迟分配。操作系统会将这些页面标记为已使用,但不会为它们分配任何物理内存。当您的代码引用内存时,它将出错,此时操作系统将实际将这些页面Map到物理内存,以便您可以使用它们。
通常情况下,操作系统会将所有分配的页面Map到单个物理零填充页面,这样就不会有读取开销。标签:Why malloc+memset is slower than calloc?
现在,如果您查看
man 1 free
,您将看到它始终显示物理内存使用情况。这与虚拟内存的怪癖无关。js5cn81o2#
这并没有什么奇怪的。
由于你的read没有做任何事情,它正在被编译器删除。
你必须强迫编译器做一些事情。你可以使用volatile数组或者放置一个内存屏障
范例:
smdnsysy3#
函数malloc()将分配一个内存块,接收到的内存块的内容没有初始化。这意味着它只是内存管理中的记录。
当你只是“读”它的时候,我想编译器会把它优化到什么都没有。您可以通过检查汇编代码来确认这一点。