稀疏矩阵如何影响内存使用?

How are sparse matrices affecting memory usage?

在下面的示例中,我创建了一个带有零的大 numpy 对象,将一个随机数放在对角线上,然后转换为 scipy 稀疏矩阵。我的内存使用报告来自任务管理器。

>>> import sys, random
>>> import numpy as np
>>> from scipy import sparse
## Memory in use at this point: 3.1 Gb
>>> m = np.zeros(shape = (40000, 40000), dtype = float)
>>> sys.getsizeof(m)
12800000112
## Memory in use at this point: 3.3 Gb
>>> for i in range(40000):
        m[i][i] = round(random.random(),3)        
>>> sys.getsizeof(m)
12800000112
## Memory in use at this point: 3.3 Gb
>>> mSp = sparse.csr_matrix(m)
>>> sys.getsizeof(mSp)
56
## Memory in use at this point: 14.9 Gb
>>> del m
## Memory in use at this point: 3.1 Gb

我的问题是,为什么在创建稀疏矩阵期间内存使用量会跳到 15 GB,而当我删除最初只占用 200 Mb 内存的原始 numpy 对象时才下降到 3.1 Gb?

我怀疑这与使用中的内存与提交的内存有关,但我很难理解该机制。

编辑:我是 运行 这个 Windows 10

这与稀疏矩阵无关,而是与现代操作系统分配内存的方式有关:请求内存时,您的 OS 会立即 return 分配地址,但不会实际分配物理内存中的页面。只有在第一次接触页面中的数据(读取或写入)时,才会分配每个单独的页面。由于您只设置了几个值,因此只会分配几个页面,所有未触及的页面实际上都不在内存中。

这通常显示为虚拟 (VIRT) 内存和物理 (PHYS) 内存。在 VIRT 中考虑但在 PHYS 中不存在的所有内容都是已分配但尚未触及的内存。

您发现内存消耗增加,因为将矩阵转换为 sparse.csr_matrix 需要 SciPy 来读取整个数组。这反过来又使您的操作系统分配所有页面并用零填充它们。

要理解这一点,我们可以使用以下示例:在导入任何内容之前,我的 ipython 内核位于

# 2GB VIRT, 44MB PHYS

我们分配内存但用零填充它,所以我们没有触及任何东西。我们使用了很多 VIRT 但几乎没有 PHYS RAM。

import numpy
array = numpy.zeros((10000, 50000))
# 6GB VIRT, 50MB PHYS

用随机值设置对角线后,我们看到 PHYS 仅略有增加,因为我们的大部分页面实际上还没有实际存在于 RAM 中。

# this is a more efficient way of setting the main diagonal of your array, by the way
array[numpy.arange(10000), numpy.arange(10000)] = numpy.random.rand(10000)
# 6GB VIRT, 90MB PHYS

如果我们现在计算数组的总和,使用量突然增加,因为读取内存触发物理分配:

numpy.sum(array)
# 6GB VIRT, 4GB PHYS

在创建一个填充了随机值的数组时也是如此:所有这些都是立即物理分配的。

array = numpy.random.rand(10000, 50000)
# 6GB VIRT, 4GB PHYS

这就是为什么建议直接以稀疏格式创建稀疏数组的原因:

import scipy.sparse
sparse_array = scipy.sparse.dok_matrix((10000, 50000))
# 2GB VIRT, 50MB PHYS

DOK 允许索引,所以我们可以高效地做

sparse_array[numpy.arange(10000), numpy.arange(10000)] = numpy.random.rand(10000)
# 2GB VIRT, 54MB PHYS

并允许高效转换为 CSR:

csr_sparse_array = scipy.sparse.csr_matrix(sparse_array)
# 2GB VIRT, 54MB PHYS

这些值是在 OSX 上计算的,但一般原则适用于 Linux、OSX 和 Windows.