您现在的位置是:亿华云 > IT科技
用 NumPy 中的视图来节省内存
亿华云2025-10-04 00:39:30【IT科技】4人已围观
简介如果您使用 Python 的 NumPy 库,通常是因为您正在处理占用大量内存的大型数组。为了减少内存使用,您可能希望尽量减少不必要的重复项。NumPy 有一个内置功能,可以在许多常见情况下透明地执行
如果您使用 Python 的视图 NumPy 库,通常是节省因为您正在处理占用大量内存的大型数组。为了减少内存使用,内存您可能希望尽量减少不必要的视图重复项。
NumPy 有一个内置功能,节省可以在许多常见情况下透明地执行此操作:内存视图。内存而且,视图此功能还可以防止数组被垃圾回收,节省从而导致更高的内存内存使用率。在某些情况下,视图它可能会导致错误,节省数据会以意想不到的内存方式发生变异。
为了避免这些问题,视图让我们了解视图的节省工作原理以及对代码的影响。
预备知识:Python 列表
在查看 NumPy 数组和视图之前,内存让我们考虑一个有点相似的数据结构:Python 列表。
Python 列表与 NumPy 数组一样,是连续的内存块。当你对一个 Python 列表进行切片时,你会得到一个完全不同的列表,这意味着你正在分配一块新的内存:
>>> from psutil import Process >>> Process().memory_info().rss 12247040 >>> list1 = [None] * 1_000_000 >>> Process().memory_info().rss 20463616 >>> list2 = list1[:500_000] >>> Process().memory_info().rss 24580096切片列表分配了更多内存。源码库由于第二个列表是一个独立的副本,如果我们改变它,这不会影响第一个列表:
>>> list2[0] = "abc" >>> print(list2[0]) abc >>> print(list1[0]) None注意,复制到第二个列表中的数据是指向 Python 对象的指针,而不是对象本身的内容。因此,即使列表本身不同,底层对象仍然在两者之间进行共享。
切片时 NumPy 数组并不进行复制
NumPy 数组的工作方式不同。因为假设您可能正在处理非常大的数组,所以许多操作不会复制数组,它们只是让您查看原始数组指向的同一连续内存块。
第一个结果是切片不会分配更多内存,因为它只是原始数组的视图:
>>> from psutil import Process >>> import numpy as np >>> arr = np.arange(0, 1_000_000) >>> Process().memory_info().rss 37810176 >>> view = arr[:500_000] >>> Process().memory_info().rss 37810176视图对象看起来像一个 500,000 长的 int64 数组,因此如果它是一个新数组,它将分配大约 4MB 的内存。香港云服务器但它只是针对同一个原始数组的一个视图,所以不需要额外的内存。
从技术上来说,可能会为视图对象本身分配一小部分内存,但这可以忽略不计,除非您有很多视图对象。在这种情况下,RSS(常驻内存)度量中没有出现新内存,因为 Python 预先分配了更大的内存块,然后用小的 Python 对象填充这些块。
视图导致内存泄漏
使用视图的后果之一是您可能会泄漏内存,而不是节省内存。这是因为视图可以防止原始数组被垃圾回收 - 对整个数组来说。
假设您已经决定只需要使用大数组的一小部分:
>>> import numpy as np >>> from psutil import Process >>> arr = np.arange(0, 100_000_000) >>> Process().memory_info().rss 830181376 >>> small_slice = arr[:10] >>> del arr >>> Process().memory_info().rss 830111744如果这是一个 Python 列表,删除原始对象将释放内存。然而,在这种情况下,即使我们没有对数组的直接引用,视图仍然可以起作用,这意味着内存没有被释放,即使我们只对其中的云南idc服务商一小部分感兴趣。
您实际上可以通过视图访问原始数组:
>>> small_slice array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> small_slice.base array([0, 1, 2, ..., 99999997, 99999998, 99999999])结果,只有当我们删除所有视图时,原始数组的内存才会被释放:
>>> del small_slice >>> Process().memory_info().rss 29642752其他改变
使用视图的另一个后果是修改视图会改变原始数组。回想一下,对于 Python 列表,修改切片结果不会修改原始列表,因为新对象是一个副本:
>>> l = [1, 2, 3] >>> ll2 = l[:] >>> l2[0] = 17 >>> l2 [17, 2, 3] >>> l [1, 2, 3]使用 NumPy 视图后,改变视图确实改变了原始对象,它们都指向同一个内存地址:
>>> arr = np.array([1, 2, 3]) >>> view = arr[:] >>> view[0] = 17 >>> view array([17, 2, 3]) >>> arr array([17, 2, 3])这个结果不是我们想要的!
由于某些 NumPy API 可能会根据情况返回视图或副本,因此更有可能发生意外变化。例如,某些切片结果可能不是视图:
>>> arr = np.array([1, 2, 3]) >>> arrarr2 = arr[:] >>> arr2.base is arr True >>> arrarr3 = arr[[True, False, True]] >>> arr3.base is arr False改变 arr2 也会改变 arr,但改变 arr3 不会改变 arr。
使用 copy() 进行显式复制
当您不想引用原始内存时,显式复制允许您创建一个新数组。这对于防止改变很有用,并且在您不想将原始数组保留在内存中的情况下也很有用:
>>> arr = np.arange(0, 100_000_000) >>> Process().memory_info().rss 829464576 >>> small_slice = arr[:10].copy() >>> del arr >>> Process().memory_info().rss 29700096 >>> print(small_slice.base) None在这种情况下,删除 arr 释放了内存,因为 small_slice 是副本,而不是视图。
要点:高效安全地使用视图
鉴于各种 NumPy API 会自动返回视图,您需要在编写代码时考虑它们:
•在文档中注意 API 是否会返回视图、副本或两者。
•如果您想从内存中清除一个大数组,请确保不仅没有直接引用它,而且没有引用它的视图。
•如果你要改变一个数组,确保它不会因为它实际上是一个视图而意外改变其他一些数组。
•如果您不需要视图,请使用 copy() 方法。
很赞哦!(4)
相关文章
- 评估域名涉及的行业规模与发展状况成正比。
- Ahooks 的 UseClickAway 在 React 17 中不工作了,该怎么办?
- 介绍网站被劫持的表现及常见手段
- 提高分层 SQL 结构的性能
- 因为域名解析需要同步到DNS根服务器,而DNS根服务器会不定时刷,只有DNS根服务器刷新后域名才能正常访问,新增解析一般会在10分钟左右生效,最长不会超过24小时,修改解析时间会稍微延长。
- SQL语言:DDL、DML、DQL、DCL详解
- 分享AWS的Redis如何正确迁移至阿里云项目实践
- 注册域名有哪些关键点和要求?
- 公司名字不但要与其经营理念、活动识别相统一,还要能反映公司理念,服务宗旨、商品形象,从而才能使人看到或听到公司的名称就能产生愉快的联想,对商店产生好感。这样有助于公司树立良好的形象。
- 2.14情人节,程序员该如何绝地反击?