在 python 3.7.2 中导入 numpy 后的 RAM 使用情况

RAM usage after importing numpy in python 3.7.2

我 运行 conda 4.6.3 和 python 3.7.2 win32。在 python 中,当我导入 numpy 时,我看到 RAM 使用量增加了 80MB。由于我使用的是多处理,我想知道这是否正常,是否有办法避免这种 RAM 开销?请在下面查看相关软件包的所有版本(来自 conda 列表):

python...........3.7.2 h8c8aaf0_2

mkl_fft...........1.0.10 py37h14836fe_0

mkl_random..1.0.2 py37h343c172_0

numpy...........1.15.4 py37h19fb1c0_0

numpy-base..1.15.4 py37hc3f5095_0

谢谢!

您无法避免这种成本,但它可能并不像看起来那么糟糕。 numpy 库(仅 C 的副本 libopenblasp,加上所有 Python numpy 扩展模块)在磁盘上占用超过 60 MB,它们都将是导入时映射到您的 Python 进程的内存;添加所有 Python 模块以及加载和初始化所有模块所涉及的动态分配内存,报告的 RAM 使用量增加 80 MB 是很正常的。

也就是说:

  1. C 库和 Python 扩展模块是内存映射的,但这并不意味着它们实际上占用 "real" RAM;如果未执行给定页面中的代码路径,则该页面将永远不会被加载,或者将在内存压力下被丢弃(甚至不会写入页面文件,因为它总是可以从原始 DLL 重新加载它)。
  2. 在类 UNIX 系统上,当您 forkmultiprocessing 默认情况下到处都这样做,但 Windows)内存在父进程和工作进程之间共享写模式。由于通常不编写代码本身,唯一的成本是页表本身(它们引用的内存的一小部分),父子将共享该 RAM。

遗憾的是,在 Windows 上,fork 不是一个选项(除非你在 运行 Ubuntu bash 上 Windows ,在这种情况下,它只是勉强 Windows,有效 Linux),因此您可能会在每个进程中支付更多的内存成本。但即使在那里,支持 numpy 大部分的 C 库 libopenblasp 也会按进程重新映射,但是 OS 应该正确地跨进程(和大部分)共享只读内存,如果不是全部,还有 Python 扩展模块)。

基本上,在这真正导致问题之前(而且不太可能这样做),请不要担心。

[NumPy]: NumPy

is the fundamental package for scientific computing with Python.

它是一个 包,旨在处理大型数据集并(主要)针对速度进行了优化。
如果您查看其 __init__.py(在导入时执行(例如:import numpy)),您会注意到它导入许多项目(包/模块):

  • 这些项目本身,可以导入其他项目
  • 其中一些是扩展模块 (.pyds (.dlls)或 .sos) 加载到当前进程(以及它们的依赖项)

我准备了一个demo

code.py:

#!/usr/bin/env python3

import sys
import os
import psutil
#import pprint


def main():
    display_text = "This {:s} screenshot was taken. Press <Enter> to continue ... "
    pid = os.getpid()
    print("Pid: {:d}\n".format(pid))
    p = psutil.Process(pid=pid)
    mod_names0 = set(k for k in sys.modules)
    mi0 = p.memory_info()

    input(display_text.format("first"))

    import numpy

    input(display_text.format("second"))

    mi1 = p.memory_info()
    for idx, mi in enumerate([mi0, mi1], start=1):
        print("\nMemory info ({:d}): {:}".format(idx, mi))

    print("\nExtra modules imported by `{:s}` :".format(numpy.__name__))
    print(sorted(set(k for k in sys.modules) - mod_names0))
    #pprint.pprint({k: v for k, v in sys.modules.items() if k not in mod_names0})
    print("\nDone.")


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

输出:

[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q054675983]> "e:\Work\Dev\VEnvs\py_064_03.06.08_test0\Scripts\python.exe" code.py
Python 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 bit (AMD64)] on win32

Pid: 27160

This first screenshot was taken. Press <Enter> to continue ...
This second screenshot was taken. Press <Enter> to continue ...

Memory info (1): pmem(rss=15491072, vms=8458240, num_page_faults=4149, peak_wset=15495168, wset=15491072, peak_paged_pool=181160, paged_pool=180984, peak_nonpaged_pool=13720, nonpaged_pool=13576, pagefile=8458240, peak_pagefile=8458240, private=8458240)

Memory info (2): pmem(rss=27156480, vms=253882368, num_page_faults=7283, peak_wset=27205632, wset=27156480, peak_paged_pool=272160, paged_pool=272160, peak_nonpaged_pool=21640, nonpaged_pool=21056, pagefile=253882368, peak_pagefile=253972480, private=253882368)

Extra modules imported by `numpy` :
['_ast', '_bisect', '_blake2', '_compat_pickle', '_ctypes', '_decimal', '_hashlib', '_pickle', '_random', '_sha3', '_string', '_struct', 'argparse', 'ast', 'atexit', 'bisect', 'copy', 'ctypes', 'ctypes._endian', 'cython_runtime', 'decimal', 'difflib', 'gc', 'gettext', 'hashlib', 'logging', 'mtrand', 'numbers', 'numpy', 'numpy.__config__', 'numpy._distributor_init', 'numpy._globals', 'numpy._import_tools', 'numpy.add_newdocs', 'numpy.compat', 'numpy.compat._inspect', 'numpy.compat.py3k', 'numpy.core', 'numpy.core._internal', 'numpy.core._methods', 'numpy.core._multiarray_tests', 'numpy.core.arrayprint', 'numpy.core.defchararray', 'numpy.core.einsumfunc', 'numpy.core.fromnumeric', 'numpy.core.function_base', 'numpy.core.getlimits', 'numpy.core.info', 'numpy.core.machar', 'numpy.core.memmap', 'numpy.core.multiarray', 'numpy.core.numeric', 'numpy.core.numerictypes', 'numpy.core.records', 'numpy.core.shape_base', 'numpy.core.umath', 'numpy.ctypeslib', 'numpy.fft', 'numpy.fft.fftpack', 'numpy.fft.fftpack_lite', 'numpy.fft.helper', 'numpy.fft.info', 'numpy.lib', 'numpy.lib._datasource', 'numpy.lib._iotools', 'numpy.lib._version', 'numpy.lib.arraypad', 'numpy.lib.arraysetops', 'numpy.lib.arrayterator', 'numpy.lib.financial', 'numpy.lib.format', 'numpy.lib.function_base', 'numpy.lib.histograms', 'numpy.lib.index_tricks', 'numpy.lib.info', 'numpy.lib.mixins', 'numpy.lib.nanfunctions', 'numpy.lib.npyio', 'numpy.lib.polynomial', 'numpy.lib.scimath', 'numpy.lib.shape_base', 'numpy.lib.stride_tricks', 'numpy.lib.twodim_base', 'numpy.lib.type_check', 'numpy.lib.ufunclike', 'numpy.lib.utils', 'numpy.linalg', 'numpy.linalg._umath_linalg', 'numpy.linalg.info', 'numpy.linalg.lapack_lite', 'numpy.linalg.linalg', 'numpy.ma', 'numpy.ma.core', 'numpy.ma.extras', 'numpy.matrixlib', 'numpy.matrixlib.defmatrix', 'numpy.polynomial', 'numpy.polynomial._polybase', 'numpy.polynomial.chebyshev', 'numpy.polynomial.hermite', 'numpy.polynomial.hermite_e', 'numpy.polynomial.laguerre', 'numpy.polynomial.legendre', 'numpy.polynomial.polynomial', 'numpy.polynomial.polyutils', 'numpy.random', 'numpy.random.info', 'numpy.random.mtrand', 'numpy.testing', 'numpy.testing._private', 'numpy.testing._private.decorators', 'numpy.testing._private.nosetester', 'numpy.testing._private.pytesttester', 'numpy.testing._private.utils', 'numpy.version', 'pathlib', 'pickle', 'pprint', 'random', 'string', 'struct', 'tempfile', 'textwrap', 'unittest', 'unittest.case', 'unittest.loader', 'unittest.main', 'unittest.result', 'unittest.runner', 'unittest.signals', 'unittest.suite', 'unittest.util', 'urllib', 'urllib.parse']

Done.

以及(before and after import)截图([MS.Docs]: Process Explorer):

作为个人评论,我认为 ~80 MiB(或任何确切数量)对于当前的 "era" 来说已经足够了以高得离谱的硬件资源为特征,尤其是在内存领域。
此外,与阵列本身所需的数量相比,这可能微不足道。如果不是这种情况,您可能应该考虑远离 numpy.

可能有一种方法可以减少内存占用,方法是有选择地仅导入包含您需要的功能的模块(我个人的建议是反对它),然后绕过 __init__.py: