【问题标题】:Run out of memory in cythoncython 内存不足
【发布时间】:2019-06-07 16:25:22
【问题描述】:

我正在尝试在 cython 中实现一个算法。但是我的BitSet 实现内存不足。我不知道为什么,这里是我的代码的有用快照。

bitset.pyx

# distutils: language = c++
from libcpp.vector cimport vector


cdef class BitSet:

    def __cinit__(self, int size):
        self.vector = vector[bint](size)

    cpdef void inter(self, BitSet other) except *:
        # Do the intersection in place between two bitset

    cpdef void add(self, int element):
        if 0 <= element < self.vector.size():
            self.vector[element] = True

bitset.pxd

# distutils: language = c++
from libcpp.vector cimport vector


cdef class BitSet:
    cdef public vector[bint] vector
    cpdef void inter(self, BitSet other) except *
    cpdef void add(self, int element)

我需要创建一个 python listBitSet(大约 12_000),每个大小为 1_000_000。我会说这应该占用 1_000_000 位(存储 bint 类型)* 12000 = 1.5 GB

但是我的内存很快耗尽,这里有一张图来解释更多

曲线的最低点为 1.5 GB,最高点为 7 GB。

我用一个巨大的列表调用这个程序,这可以解释第一个峰值,第二个可能是我面临的那个。

我的程序仅在 100_000 个序列后就耗尽了内存。

这是我的主要内容:

cdef class Main:
    def __cinit__(self):
        self.number_sequences # Int
        self.foo = [] # python list type

    def train(self, sequences):
        self.number_sequences = len(sequences)
        for id_seq, sequence in enumerate(sequences):
            for element in sequence:
                while not element < len(self.foo):
                    self.foo.append(BitSet(self.number_sequences))
                self.foo[element].add(id_seq)

我对内存使用的估计是否错误?为什么?

如何追踪我的记忆?我没有找到任何用于 cython 的工具。

有没有办法让它适合内存? (用 python 的 ints 集替换 BitSet 可以,但速度要慢得多,应该占用更多空间)

【问题讨论】:

  • bint 将(至少)是一个字节而不是一个位。使用 C++ std::bitset 可能会更好,但我认为 Cython 没有预先编写的包装器,或者优化的 std::vector&lt;bool&gt;,尽管我不能 100% 确定它的状态
  • 我不知道。它可以解释很多事情。你怎么知道的?
  • 您可以通过print(sizeof(bint)) 对其进行测试(而不是信任我)。本质上,C/C++ 中的任何变量都必须占用至少一个字节。任何更有效的方案都将存储 uint8_ts(或类似的),然后访问其中的各个位 - 这是 bitsetvector&lt;bool&gt; 所做的,但速度成本很小
  • ....尽管请记住 sizeof 以字节为单位返回答案(这暗示了所有内容的大小必须以整个字节为单位)
  • 经过调查,一个 bint 存储在 4 Bytes 而不是 1/8B 上。它将它乘以 32。如果您写一个答案@DavidW,我可能会接受它,我将使用 uint8_t 优化我的代码! :)

标签: memory-management cython


【解决方案1】:

bint 只是一种方便的整数类型,可用于存储真/假值。与所有其他 C 类型一样,它需要一个可以以整个字节为单位测量的地址,因此它至少占用 1 个字节(实际上看起来更多)。

为了以节省空间的方式存储布尔值,您需要存储整数类型,然后进行一些操作以访问单个元素:

cdef uint8_t x = some_value
nth_element_is_true = bool(x & (1<<n)) # bitshift to get a suitable mask then bitwise and

您显然可以扩展它以使用数组来存储多个元素。


存在一些明显的预制可能性:

首先你可以使用numpy.packbitsunpackbits。不过,这确实会生成相当昂贵的临时变量(例如,unpackbits 将创建一个大小为 8 倍的数组)。

其次,您可以使用 std::vector&lt;bool&gt;,它已经优化为每个元素使用 1 位:

from libcpp.vector cimport vector
from libcpp cimport bool
cdef vector[bool] vb = vector[bool](1000000)

创建一个与普通vector 行为不同完全的专用vector&lt;bool&gt; 在事后看来在 C++ 中被认为是一个坏主意,但它可以满足您的需求。

【讨论】:

    猜你喜欢
    • 2014-10-15
    • 2011-05-21
    • 2021-12-20
    • 2021-10-29
    • 1970-01-01
    • 2020-03-12
    • 2015-08-11
    • 2019-04-19
    • 2021-05-14
    相关资源
    最近更新 更多