mmap映射后内存占用翻倍咨询:为何进程与页缓存均占内存?
关于mmap机制内存占用翻倍的问题
问题描述
学习mmap机制时,理解是mmap将虚拟地址映射到页缓存,无需拷贝数据,系统中仅存一份数据副本。但调用mmap后再用read读取数据,发现内存占用增长为读取文件大小的两倍,疑问是理解有误还是代码问题。
测试前内存占用
$ free -m total used free shared buff/cache available Mem: 3924 1391 2280 13 251 2292 Swap: 0 0 0
测试代码
import mmap import os import time # file2.db 是一个2GB的文件 with open("/var/tmp/file2.db", "r") as f: with mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ) as mm: x = mm.read(500000000) time.sleep(10000)
执行命令:
$ python3 mmap_read.py &
测试后内存占用
$ free -m total used free shared buff/cache available Mem: 3924 1703 1575 13 644 1980 Swap: 0 0 0
perf分析结果
$ sudo perf record python3 mmap_read.py & # 记录系统调用 $ sudo perf report
输出:
Samples: 128 of event 'cpu-clock:pppH', Event count (approx.): 1292929280 Overhead Command Shared Object Symbol 24.22% python3 [kernel.kallsyms] [k] do_user_addr_fault 4.69% python3 [kernel.kallsyms] [k] rmqueue 3.91% python3 [kernel.kallsyms] [k] __add_to_page_cache_locked 3.91% python3 [kernel.kallsyms] [k] charge_memcg 3.91% python3 libc.so.6 [.] 0x00000000001a0e81 3.12% python3 [kernel.kallsyms] [k] __lock_text_start 3.12% python3 [kernel.kallsyms] [k] xas_load 3.12% python3 libc.so.6 [.] 0x00000000001a0ef0 2.34% python3 [kernel.kallsyms] [k] __mod_lruvec_state 2.34% python3 [kernel.kallsyms] [k] do_anonymous_page 2.34% python3 [kernel.kallsyms] [k] free_unref_page_list 2.34% python3 [kernel.kallsyms] [k] release_pages 2.34% python3 libc.so.6 [.] 0x00000000001a0e6f 1.56% python3 [kernel.kallsyms] [k] __cgroup_throttle_swaprate 1.56% python3 [kernel.kallsyms] [k] __mod_node_page_state 1.56% python3 [kernel.kallsyms] [k] filemap_map_pages 1.56% python3 [kernel.kallsyms] [k] pmd_page_vaddr 1.56% python3 [kernel.kallsyms] [k] pmd_pfn 1.56% python3 [kernel.kallsyms] [k] xa_get_order 1.56% python3 libc.so.6 [.] 0x00000000001a0e4c 1.56% python3 libc.so.6 [.] 0x00000000001a0e7d 1.56% python3 libc.so.6 [.] 0x00000000001a0e86 1.56% python3 libc.so.6 [.] 0x00000000001a0f47 0.78% python3 [kernel.kallsyms] [k] __bio_add_page 0.78% python3 [kernel.kallsyms] [k] __handle_mm_fault 0.78% python3 [kernel.kallsyms] [k] __mem_cgroup_charge 0.78% python3 [kernel.kallsyms] [k] __page_set_anon_rmap 0.78% python3 [kernel.kallsyms] [k] arch_local_irq_enable 0.78% python3 [kernel.kallsyms] [k] blk_mq_dispatch_rq_list 0.78% python3 [kernel.kallsyms] [k] cgroup_rstat_updated 0.78% python3 [kernel.kallsyms] [k] clear_page_erms 0.78% python3 [kernel.kallsyms] [k] do_set_pte 0.78% python3 [kernel.kallsyms] [k] elv_rqhash_add 0.78% python3 [kernel.kallsyms] [k] finish_task_switch.isra.0 0.78% python3 [kernel.kallsyms] [k] get_mem_cgroup_from_mm 0.78% python3 [kernel.kallsyms] [k] handle_mm_fault 0.78% python3 [kernel.kallsyms] [k] handle_pte_fault 0.78% python3 [kernel.kallsyms] [k] kthread_blkcg 0.78% python3 [kernel.kallsyms] [k] page_counter_try_charge 0.78% python3 [kernel.kallsyms] [k] pmd_val 0.78% python3 [kernel.kallsyms] [k] try_charge_memcg 0.78% python3 [kernel.kallsyms] [k] xas_find 0.78% python3 [kernel.kallsyms] [k] zap_pte_range 0.78% python3 libc.so.6 [.] 0x00000000001a0e5a 0.78% python3 libc.so.6 [.] 0x00000000001a0e76 0.78% python3 libc.so.6 [.] 0x00000000001a0f02 0.78% python3 libc.so.6 [.] 0x00000000001a0f07 0.78% python3 libc.so.6 [.] 0x00000000001a0f27 0.78% python3 libc.so.6 [.] 0x00000000001a0f57 0.78% python3 libc.so.6 [.] 0x00000000001a0f5f 0.78% python3 python3.10 [.] 0x000000000012161a 0.78% python3 python3.10 [.] 0x000000000012d084
疑问
原本预期buff/cache增长而used内存保持不变,因为进程应直接引用页缓存中的数据,请问这是什么原因?
解答
问题出在你调用了mm.read(500000000)这个操作。虽然mmap确实把文件映射到了进程虚拟地址空间,和页缓存共享同一份物理内存,但Python的mmap.mmap对象的read()方法并不是直接返回映射区域的引用,而是创建了一个新的字节对象,并把映射区域的数据拷贝到这个新对象里。
这就导致系统中存在两份数据:
- 页缓存中的文件数据(对应
buff/cache的增长); - Python变量
x指向的字节对象(对应used内存的增长)。
数据验证
从你的free输出可以看到:
buff/cache从251MB增长到644MB,增加了约393MB;used从1391MB增长到1703MB,增加了约312MB;
两者加起来接近你读取的500MB(因为内存统计按页对齐,还有系统其他内存变动,所以数值不是精确的500MB)。
正确用法
如果想避免拷贝,直接使用mmap对象访问数据即可,比如通过切片或者索引:
import mmap import time with open("/var/tmp/file2.db", "r") as f: with mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ) as mm: # 直接访问映射区域,不产生拷贝 first_byte = mm[0] # 或者获取视图,也不会拷贝 data_view = mm[:500000000] time.sleep(10000)
这样used内存不会明显增长,只有buff/cache会因为文件页被加载而增加,符合你最初对mmap的预期。
内容的提问来源于stack exchange,提问作者Belive
相关产品推荐
相关产品推荐

