ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python3使用tracemalloc实现追踪mmap内存变化

Python3使用tracemalloc实现追踪mmap内存变化 通过使用方法来实现对mmap内存变化情况的追踪处理。更新时间为2023年03月14日 11:11:58, 作者是。这篇文章的核心内容, 是向大家重点介绍在特定的环境中, 具体是如何来实现对mmap内存变化进行追踪的相关技术的。并且文章里头所附带的示例代码部分是经过详细讲解过的。要是你对此话题感兴趣的话, 你可以去了解一下。技术背景在前面的一篇博客里面咱们讲了一些拿来处理表格数据的方法, 这里面重点包含了像vaex这样的、处理大规模数据的方案。这个数据处理的方案是基于内存映射map这项技术, 具体来说, 是通过创建内存映射文件这一操作来实现的, 其目的在于避免直接在内存里加载源数据, 因为那样做会导致大规模地占用内存空间。正因为如此, 我们才能够在本地电脑内存规模不是非常大的那些条件下, 对大规模的数据进行处理工作。其中明确提供了 mmap这样的一个仓库设施, 利用它就能直接创建出内存映射文件。使用程序去跟踪其本身的内存消耗情况。我们在这一部分里, 心里头是想着要把内存映射技术在实际使用时候的那个内存占用情况进行一番对比的, 正因如此, 我们就必须得引进一个基于相关的内存追踪工具。现在咱们就先来看一个简单的案例, 这个案例具体是什么, 就是去创建一个随机的数组, 然后呢再去观察这个数组在内存里面占用的那个大小是多少:# tracem.pyimport tracemallocimport numpy as nptracemalloc.start()length10000test_arraynp.random.randn(length) # 分配一个定长随机数组snapshottracemalloc.take_snapshot() # 内存摄像top_statssnapshot.statistics(lineno) # 内存占用数据获取print ([Top 10])for stat in top_stats[:10]: # 打印占用内存最大的10个子进程print (stat)请提交您需要我进行文本改写的具体内容。- mmap$ .py.py:8: size78.2 KiB, count2, 39.1 KiB倘若我们选择运用top这一指令, 来实施对内存使用状况的直接检视工作, 那么毫无疑问, 在所有进程中, 谷歌浏览器所占用的内存比例是最高的:当前运行时间为上午 10:04:08, 设备已经持续在线了 6 天又 15 小时 18 分钟, 并且当前有 5 个用户正在登录使用, 系统负载水平分别是 0.23、0.33 和 0.27。任务是309个总数, 其中包含1、264和23和21这四个部分。处理器总体状态, 用户层面占0.6, 系统层面有0.2, 内核空间未调整有0.0, 空闲比例高达99.0, 等待输入输出操作占0.0, 历史中断服务请求为0.0, 硬件中断处理时间占0.0, 周期也就是虚拟机被其他任务偷走的资源时间为0.0。内存的总容量是39913.6 MiB, 其中空闲的部分达到25450.8 MiB, 正在使用的部分是1875.7 MiB, 而作为缓存和缓冲区的空间则有12587.1 MiB那么大。该交换空间的总量是16384.0兆字节, 当前的空闲大小也是16384.0兆字节, 其中被使用的部分为零点零兆字节。另外, 可供内存使用的剩余空间为36775.8兆字节。进程号 USER PR NI VIRT RES SHR %CPU %MEM TIME在二十这个数值对应零, 三十六点六克, 接着是硫这个符号, 然后是四点零、零点四, 最后比一比结果是一比零点三十二。所以我们得靠着那个进程号, 去盯着子进程看它究竟占用了多少内存, 这才是用得上的关键地方。在这之中我们发现了一个东西, 就是有一个size为一万的大小的numpy数组向量, 它所消耗的内存大概有三十九点一千字节的这么个数目, 这其实是挺符合我们的预想的情况的:In : 39.1*1024/4Out: 10009.6这是因为那个数值基本上就是10000个浮点数在内存里所占的大小, 这说明所有的元素都已经存放在内存中了。通过用追踪内存变化的方式。在上文中, 我们已经详细说明了应该如何去操作内存快照, 基于这一点, 大伙儿自然就会顺理成章地联想到这么一种思路, 那就是拿前后两次分别拍下来的两张内存快照来做个比较, 看一看里面到底发生了哪些改变, 通过这种方式难道不就能把内存具体有多少变化也算出个大概数值出来了吗?紧接着咱们就干脆照着这个想法去简单试一下:# comp_tracem.pyimport tracemallocimport numpy as nptracemalloc.start()snapshot0tracemalloc.take_snapshot() # 第一张快照length10000test_arraynp.random.randn(length)snapshot1tracemalloc.take_snapshot() # 第二张快照top_statssnapshot1.compare_to(snapshot0,lineno) # 快照对比print ([Top 10 differences])for stat in top_stats[:10]:print (stat)执行结果如下- mmap$ .pyTop 10在文件.py的第9行显示的统计信息是: 总大小为78.2 KiB, 这一数值与之前相比增加了78.2 KiB项目的计数为2, 相比之前增加了2个最后计算出的平均值为39.1 KiB。大家可以清楚地观察到, 在这个快照出现之前与之后, 平均的内存大小所产生的差异程度是处于39.1 KiB这个数值范围里面的。假如接下来操作的时候, 我们要把那个矢量所对应的维度参数内容修改成为下面的这种设定状况的话。length1000000再次执行一遍, 来观察一下具体的效果怎样。- mmap$ .pyTop 10在名为.py的第9行, 显示的参数包括大小等于7813 KiB加上7813 KiB, 计数等于2加上2, 平均值等于3906 KiB。我们看到最终得出的数字是3906, 因为被放大了100倍, 所以感觉这个结果跟心里预期差不多是可以接受的。但是呢如果咱再仔细地、认认真真地去算一把的话:In : 3906*1024/4输出结果为零点零。我们观察到, 此处的数据类型并非完全的同一类型。与那种完整的类型相比, 它在内存大小方面有所缺失。于是, 我们怀疑是否是在中间的环节生成了一些零数值, 并最终被自动压缩从而缩小了体积。然而, 这确实不是我们需要重点关注的对象。因此, 我们还是继续去测试内存的变化曲线吧。内存占用曲线顺着前两个章节的内容脉络, 咱们接下来重点做一个测试, 看看不同维度的随机数组到底得占用多少内存, 具体的做法是在之前那段代码模块的基础上头再多增加一个for循环出来。# comp_tracem.pyimport tracemallocimport numpy as nptracemalloc.start()x[]y[]multiplier{B:1,KiB:1024,MiB:1048576}snapshot0tracemalloc.take_snapshot()for length in range(1,1000000,100000):np.random.seed(1)test_arraynp.random.randn(length)snapshot1tracemalloc.take_snapshot()top_statssnapshot1.compare_to(snapshot0,lineno)for stat in top_stats[:10]:if comp_tracem.py in str(stat): # 判断是否属于当前文件所产生的内存占用x.append(length)memstr(stat).split(average)[1].split( )y.append(float(m曲线em[0])*multiplier[mem[1]])breakimport matplotlib.pyplot as pltplt.figure()plt.plot(x,y,D,colorblack,labelExperiment)plt.plot(x,np.dot(x,4),colorred,labelExpect) # float32的预期占用空间plt.title(Memery Difference vs Array Length)plt.xlabel(Number Array Length)plt.ylabel(Memory Difference)plt.legend()plt.savefig(comp_mem.png)所绘制出来的展示效果具体如下所示:这里我们又发现, 虽然大部分情况下是符合内存占用预期的, 但有很多个点比预期占用的要少。我们也怀疑是因为存在0元素, 因此稍微修改了一下代码。在原代码的基础上增加了一个操作来尽可能的避免0的出现:# comp_tracem.pyimport tracemallocimport numpy as nptracemalloc.start()x[]y[]multiplier{B:1,KiB:1024,MiB:1048576}snapshot0tracemalloc.take_snapshot()for length in range(1,1000000,100000):np.random.seed(1)test_arraynp.random.randn(length)test_arraynp.ones(length)*np.pi # 在原数组基础上加一个圆周率内存不变snapshot1tracemalloc.take_snapshot()top_statssnapshot1.compare_to(snapshot0,lineno)for stat in top_stats[:10]:if comp_tracem.py in str(stat):x.append(length)memstr(stat).split(average)[1].split( )y.append(float(mem[0])*multiplier[mem[1]])breakimport matplotlib.pyplot as pltplt.figure()plt.plot(x,y,D,colorblack,labelExperiment)plt.plot(x,np.dot(x,4),colorred,labelExpect)plt.title(Memery Difference vs Array Length)plt.xlabel(Number Array Length)plt.ylabel(Memory Difference)plt.legend()plt.savefig(comp_mem.png)在完成了后续的更新操作以后, 最终所得到的结果图像呈现状态如以下的展示图所说明的一样。虽然不符合预期的点数变少了, 但是这里还是存在两个内存占用大小与预期不符的情况, 疑似数据被压缩了。mmap内存占用测试在上述的这几个章节中, 我们其实已经大致地把内存追踪技术的运用给弄明白了。现在, 我们可以试着把这记技巧用到 mmap 这个内存映射技术里头去。这么一安排上去之后, 具体会冒出个什么样的结果来, 我们就一块儿瞅瞅看。先把那个numpy构成的数组给弄清楚了, 然后再想办法把它塞进TXT这个文件里面去。因为内存映射本质上就是一个对系统文件的读写操作, 所以, 我们这里要首先做的步骤是, 把前面那个阶段用到过的numpy数组, 给它存储到txt文件里面去:# write_array.pyimport numpy as npx[]y[]for length in range(1,1000000,100000):np.random.seed(1)test_arraynp.random.randn(length)test_arraynp.ones(length)*np.pinp.savetxt(numpy_array_length_str(length).txt,test_array)等到把内容成功地写完之后, 你去看一看那个当前的目录下面, 会发现有一大堆的.txt文件被创建了出来:-rw-r--r-- 1 4月 12 10:09 00001.txt文件权限为只读和读写, 用户数为1, 文件大小为25字节, 创建日期是4月12日, 时间是10点09分, 最后修改的拓展名为强标签.txt。-rw-r--r-- 1 4月 12 10:09 00001.txt-rw-r--r-- 1 4月 12 10:09 00001.txt-rw-r--r-- 1 4月 12 10:09 00001.txt-rw-r--r-- 1 4月 12 10:09 00001.txt-rw-r--r-- 1 4月 12 10:09 00001.txt-rw-r--r-- 1 4月 12 10:09 00001.txt-rw-r--r-- 1 4月 12 10:09 00001.txt-rw-r--r-- 1 4月 12 10:09 00001.txt我们可以利用head这个功能来查看前面的n个元素, 或者使用tail这个功能来查看后面的n个元素, 这样做的目的是为了能够方便地对数据进行初步的检查与筛选。- mmap$ head -n 5 00001.txt4.002.002.002.004.00针对numpy文件读取的这个测试任务进行一下相关的工作。在前面的多次测试过程里, 我们是直接在内存之中生成那个被称为numpy的数组并且同时对内存使用情况进行监控。这里我们为了做到更严格的对比效果, 所以统一决定采用由文件进行读取的这种操作方式。首先咱们需要去观察一下, 当读取numpy格式的文件时, 其对应的内存变化情况曲线具体会呈现为怎样的状态:# npopen_tracem.pyimport tracemallocimport numpy as nptracemalloc.start()x[]y[]multiplier{B:1,KiB:1024,MiB:1048576}snapshot0tracemalloc.take_snapshot()for length in range(1,1000000,100000):test_arraynp.loadtxt(numpy_array_length_str(length).txt,delimiter,)snapshot1tracemalloc.take_snapshot()top_statssnapshot1.compare_to(snapshot0,lineno)for stat in top_stats[:10]:if /home/dechin/anaconda3/lib/python3.8/site-packages/numpy/lib/npyio.py:1153 in str(stat):x.append(length)memstr(stat).split(average)[1].split( )y.append(float(mem[0])*multiplier[mem[1]])breakimport matplotlib.pyplot as pltplt.figure()plt.plot(x,y,D,colorblack,labelExperiment)plt.plot(x,np.dot(x,8),colorred,labelExpect)plt.title(Memery Difference vs Array Length)plt.xlabel(Number Array Length)plt.ylabel(Memory Difference)plt.legend()plt.savefig(open_mem.png)需要重点留意的一点是, 这里仍然在使用 numpy 工具来读取文件。但是, 内存占用的来源已经不再是那个名为 .py 文件的源代码了。这部分数据现在的去向, 是被存储到了 npyio.py:1153 这个程序位置里了。所以, 当我们后续进行内存占用跟踪分析的时候, 就需要把对应的统计检查位置做一些调整。最终的输出结果如下图所示:因为在数据完成读取的动作之后, 系统方面是默认按照某种既定的机制来进行后续处理的, 所以我们这边所预期到的关于内存占用的大小数值, 应该是等于元素的总数量再乘以数字8的, 而在当前这一过程里面, 实际读入的那部分数据所占据的内存空间, 其表现情况几乎是完全符合之前所设定的那个预期指标的。mmap内存占用测试在前面一大篇幅的内容里, 铺垫了很多东西, 到最后的最后, 总算是到了要对内存映射技术进行测试的这个阶段了, 实际上呢, 内存映射模块mmap它的使用方式, 要说起来倒真的也不难, 无非就是把os模块拿来, 配合着进行文件读取这一番操作罢了, 基本上就是凑齐了一行代码这么短的东西:# mmap_tracem.pyimport tracemallocimport numpy as npimport mmapimport ostracemalloc.start()x[]y[]multiplier{B:1,KiB:1024,MiB:1048576}snapshot0tracemalloc.take_snapshot()for length in range(1,1000000,100000):test_arraymmap.mmap(os.open(numpy_array_length_str(length).txt,os.O_RDWR),0) # 创建内存映射文件snapshot1tracemalloc.take_snapshot()top_statssnapshot1.compare_to(snapshot0,lineno)for stat in top_stats[:10]:print (stat)if mmap_tracem.py in str(stat):x.append(length)memstr(stat).split(average)[1].split( )y.append(float(mem[0])*multiplier[mem[1]])breakimport matplotlib.pyplot as pltplt.figure()plt.plot(x,y,D,colorblack,labelExperiment)plt.title(Memery Difference vs Array Length)plt.xlabel(Number Array Length)plt.ylabel(Memory Difference)plt.legend()plt.savefig(mmap.png)程序跑出来的结果, 就是下面这些东西。从我们观察到的情况来看内存上的波动情况几乎是微不足道的, 产生这种现象的原因是我们并没有选择将整个数组直接加载到内存里面去。相反地, 我们在内存当中加载的是该文件的内存映射版本。通过这样的方式我们能够实现读取文件中任意位置上的字节数据的操作, 并且在此过程中我们是不需要耗费过大的内存资源的。我们在对文件进行写入操作的时候, 一定要注意谨慎行事, 因为若是使用内存映射这项技术的话, 数据的字节总数必须要固定不变, 一旦数量发生了变化, 那么就会导致内存映射出现错误情况的发生。总结概要这篇文章讲了一下用来追踪程序内存的情况的技术, 还讲了简单的叫 mmap 的文件映射技术的用法和示范。从这些例子能看到, 要是算的东西不多, 能把所有要算的元素都放在内存里这样既省事又快。对于处理大规模文件的情形, 采用内存映射技术可以获得更为快捷的效果, 这一速度优势在我们本文所介绍的多个案例的实际运行过程中是可以切实感受到的, 并且需要注意的是, 内存映射技术在目前众多不同的应用场景里都已经得到了非常广泛的使用, 诸如之前我们所提及过的vaex这个项目, 之所以能够取得优异的性能表现, 主要就是因为它充分受惠于内存映射技术所带来的便利。到这里为止, 关于如何实现跟踪内存变化的内容就介绍完毕了。如果想要了解更多与跟踪内存变化相关的知识, 推荐去搜索脚本之家发布的既往文章进行阅读, 也可以继续翻阅下文中的相关推荐栏目。期望大家往后能够持续支持脚本之家平台。
返回列表