numpy -- 就地将非连续数据转换为连续数据

6 投票
3 回答
3226 浏览
提问于 2025-04-17 19:10

考虑以下代码:

import numpy as np
a = np.zeros(50)
a[10:20:2] = 1
b = c = a[10:40:4]
print b.flags  # You'll see that b and c are not C_CONTIGUOUS or F_CONTIGUOUS

我的问题是:

有没有办法(只通过对 b 的引用)让 bc 变得连续?如果在这个操作后 np.may_share_memory(b,a) 返回 False 也没关系。

接近但不完全可行的方法有: np.ascontiguousarraynp.asfortranarray,因为它们会返回一个 新的 数组。


我的使用场景是,我有非常大的 3D 数据字段,这些字段存储在一个 numpy.ndarray 的子类中。为了节省内存,我想把这些字段裁剪到我实际想处理的那部分区域:

a = a[ix1:ix2,iy1:iy2,iz1:iz2]

对于这个子类,切片的限制比 ndarray 对象要多一些,但这应该可以工作,并且会“做正确的事情”——附加在子类上的各种自定义元数据会按预期被转换/保留。不幸的是,由于这返回的是一个 view,numpy 不会在之后释放大数组,所以我在这里并没有真正节省内存。

为了完全清楚,我想完成两件事:

  • 保留我类实例上的元数据。切片可以工作,但我不确定其他复制方式是否有效。
  • 确保原始数组可以被垃圾回收。

3 个回答

1

我认为你提到的两件事的正确做法是通过使用 np.copy 来复制你创建的切片。

当然,要让这个方法正常工作,你需要定义一个合适的 __array_finalize__。你没有很清楚地说明为什么一开始选择不使用它,但我觉得你应该去定义它。(你是怎么解决 bx**2 的问题而不使用 __array_finalize__ 的呢?)

6

根据Alex Martelli的说法

“确保大量临时使用的内存在完成后能将所有资源返回给系统的唯一可靠方法,就是让这些使用发生在一个子进程中,这个子进程负责处理内存需求大的工作,然后再结束。”

不过,以下内容似乎能释放至少一部分内存:警告:我测量可用内存的方法是针对Linux系统的:

import time
import numpy as np

def free_memory():
    """
    Return free memory available, including buffer and cached memory
    """
    total = 0
    with open('/proc/meminfo', 'r') as f:
        for line in f:
            line = line.strip()
            if any(line.startswith(field) for field in ('MemFree', 'Buffers', 'Cached')):
                field, amount, unit = line.split()
                amount = int(amount)
                if unit != 'kB':
                    raise ValueError(
                        'Unknown unit {u!r} in /proc/meminfo'.format(u=unit))
                total += amount
    return total

def gen_change_in_memory():
    """
    https://stackoverflow.com/a/14446011/190597 (unutbu)
    """
    f = free_memory()
    diff = 0
    while True:
        yield diff
        f2 = free_memory()
        diff = f - f2
        f = f2
change_in_memory = gen_change_in_memory().next

在分配大数组之前:

print(change_in_memory())
# 0

a = np.zeros(500000)
a[10:20:2] = 1
b = c = a[10:40:4]

在分配大数组之后:

print(change_in_memory())
# 3844 # KiB

a[:len(b)] = b
b = a[:len(b)]
a.resize(len(b), refcheck=0)
time.sleep(1)

调整大小后可用内存增加:

print(change_in_memory())
# -3708 # KiB
3

你可以在cython中这样做:

In [1]:
%load_ext cythonmagic

In [2]:
%%cython
cimport numpy as np

np.import_array()

def to_c_contiguous(np.ndarray a):
    cdef np.ndarray new
    cdef int dim, i
    new = a.copy()
    dim = np.PyArray_NDIM(new)
    for i in range(dim):
        np.PyArray_STRIDES(a)[i] = np.PyArray_STRIDES(new)[i]
    a.data = new.data
    np.PyArray_UpdateFlags(a, np.NPY_C_CONTIGUOUS)
    np.set_array_base(a, new)

In [8]:
import sys
import numpy as np
a = np.random.rand(10, 10, 10)
b = c = a[::2, 1::3, 2::4]
d = a[::2, 1::3, 2::4]
print sys.getrefcount(a)
to_c_contiguous(b)
print sys.getrefcount(a)
print np.all(b==d)

输出结果是:

4
3
True

to_c_contiguous(a) 会创建一个 a 的c连续副本,并将其作为 a 的基础。

在调用 to_c_contiguous(b) 之后,a 的引用计数会减少,当 a 的引用计数变为0时,它就会被释放。

撰写回答