【问题标题】:Segmentation fault when creating multiprocessing array创建多处理数组时出现分段错误
【发布时间】:2018-12-13 08:35:06
【问题描述】:

我正在尝试使用多处理填充一个 numpy 数组,遵循this post。我在我的 Mac 上运行良好,但是当我将它移植到 Ubuntu 时,我经常遇到分段错误。

我已将代码简化为以下最小示例:

import numpy as np
from multiprocessing import sharedctypes

a = np.ctypeslib.as_ctypes(np.zeros((224,224,3)))
print("Made a, now making b")
b = sharedctypes.RawArray(a._type_, a)
print("Finished.")

在 Ubuntu 16.04 上,使用 Python 3.6.5 和 numpy 1.15.4(与我的 Mac 上的版本相同),我得到了输出

Made a, now making b
Segmentation fault (core dumped)

现在,我可以稍微更改数组维度,并且在某些情况下它会起作用(例如,将前 224 更改为 100 并且它会起作用)。但大多数情况下它会出现故障。

谁能提供任何见解?

我看到了one post on a related topic from 2016 that no one responded toanother one involving pointers,我没有使用它们。

PS-a 指定为多维数组或扁平数组(例如np.zeros(224*224*3))似乎没有任何区别。如果我更改数据类型(例如将浮点数更改为整数),它似乎也没有什么不同;它同样失败。

进一步更新: 即使在 original post 的代码中设置“size=224”也会导致两台具有不同 numpy 版本的不同 Ubuntu 机器上出现 seg 错误,但在 Mac 上可以正常工作。

【问题讨论】:

  • 在我的机器上尝试运行MMAP时会发生这种情况。
  • np.zeros((224,224), dtype=np.float32) 创建你的零点有什么不同吗?似乎 float32float64(标准 CPython float 的默认大小)不匹配发生在某处,因此可能足以“修复”它。
  • @tel 这行得通。以下数组适用于 float64 (1, 36861) 和段 (1, 36862) 而不出所料,以下数组适用于 float32 (1, 73722) 并在 (1, 73723) 失败。
  • @tel 那没用。 float32 或 float64 没有区别。

标签: python numpy python-multiprocessing


【解决方案1】:

这更像是一个猜测而不是答案,但由于底层数据缓冲区的垃圾收集,您可能会遇到问题。这可以解释为什么似乎依赖于您尝试创建的数组的整体大小。

如果是这种情况,那么解决方法是将您创建的 Numpy 零数组分配给它自己的变量。这将确保缓冲区通过RawArray 的创建“存活”。代码将是:

zs = np.zeros((224,224,3))
a = np.ctypeslib.as_ctypes(zs)
print("Made a, now making b")
b = sharedctypes.RawArray(a._type_, a)
print("Finished.")

我现在只有一个 mac,所以我自己无法测试。

【讨论】:

  • ubuntu WSL 上测试并且可以工作!不错的猜测。对于更大的数字也是如此。
  • 还有 Debian。您绝对应该提交错误报告。
  • @tel,做到了!哇,只需使用中间变量名称(“zs”)而不是相同的变量(“a”)就可以了。
【解决方案2】:

其他分析和根本原因修复。

如上所述,这是垃圾收集错误的结果,这给了我关于如何修复它的提示。

通过保留对原始np.zeros 对象的引用,避免了该错误。这意味着(对我而言)原始对象的集合破坏了结果数组。

as_ctypes的实现(取自c52543e4a

def as_ctypes(obj):
    """Create and return a ctypes object from a numpy array.  Actually
    anything that exposes the __array_interface__ is accepted."""
    ai = obj.__array_interface__
    if ai["strides"]:
        raise TypeError("strided arrays not supported")
    if ai["version"] != 3:
        raise TypeError("only __array_interface__ version 3 supported")
    addr, readonly = ai["data"]
    if readonly:
        raise TypeError("readonly arrays unsupported")
    tp = _ctype_ndarray(_typecodes[ai["typestr"]], ai["shape"])
    result = tp.from_address(addr)
    result.__keep = ai
    return result

很明显,原作者想到了这一点(分配.__keep 以维护对原始对象的引用)。但是,他们似乎需要保留对 original 对象的引用。

我已经写了a patch 这样做:

-        result.__keep = ai
+        result.__keep = obj

【讨论】:

    【解决方案3】:

    最后说明

    将我的测试留给后代,但tel 有答案。

    注意

    以下测试结果是在 Debian 上的。在 Ubuntu (WSL) 上进行测试确实要糟糕得多。在 Ubuntu n=193 上,任何形状崩溃(如果我将 3d n 替换为 1),以及上面的任何 n。看起来像(见下面的bla.py):

    1. py bla.py n 1A 上分配 3204,在 B 上为 al 0<n<193 分配 29323
    2. 对于n>=193B 上发生分段错误,3208 分配在A 上。显然,ubuntu 的某处存在一些硬内存限制。

    Debian 上的旧测试

    经过一些测试,它看起来像是一个内存问题,内存分配随维度的奇怪缩放。

    只有 2 个维度的编辑对我来说不会崩溃,但 3 个会崩溃 - 我会假设这个来回答。

    对我来说:

    b = sharedctypes.RawArray(a._type_, a)
    

    在以下情况下不会崩溃:

    a = np.ctypeslib.as_ctypes(np.zeros((224**3))) #Though generating b takes a while
    a = np.ctypeslib.as_ctypes(np.zeros((100,100,100)))
    

    因此,似乎对内存的需求减少就解决了问题,但奇怪的是,一维数组中所需的相同数量的单元格工作正常 - 所以内存中更深的东西似乎正在发生。

    当然,您使用的是指针。让我们尝试一些事情(bla.py):

    import tracemalloc
    import numpy as np
    from sys import argv
    from multiprocessing import sharedctypes
    
    n,shape = (int (x) for x in argv[1:])
    if shape == 1: shape = n
    if shape == 2: shape = (n**2,n)
    if shape == 3: shape = (n,n,n)
    
    tracemalloc.start()
    a = np.ctypeslib.as_ctypes(np.zeros(shape))
    x=tracemalloc.take_snapshot().statistics('lineno')
    print(len(x),sum((a.size for a in x)))
    b = sharedctypes.RawArray(a._type_, a)
    x=tracemalloc.take_snapshot().statistics('lineno')
    print(len(x),sum((a.size for a in x)))
    

    导致:

               n   shape    (a mallocs sum) (b mallocs sum)
    >py bla.py 100 1     => 5 3478 76 30147
    >py bla.py 100 2     => 5 5916 76 948313
    >py bla.py 100 3     => 5 8200 76 43033
    >py bla.py 150 1     => 5 3478 76 30195
    >py bla.py 150 2     => 5 5916 76 2790461
    >py bla.py 150 3     => 5 8200 76 45583
    >py bla.py 159 1     => 5 3478 76 30195
    >py bla.py 159 2     => 5 5916 76 2937854
    >py bla.py 159 3     => 5 8200 76 46042
    >py bla.py 160 1     => 5 3478 76 30195
    >py bla.py 160 2     => 5 5916 72 2953989
    >py bla.py 160 3     => 5 8200 Segmentation fault
    >py bla.py 161 1     => 5 3478 76 30195
    >py bla.py 161 2     => 5 5916 75 2971746
    >py bla.py 161 3     => 5 8200 75 46116
    
    >py bla.py 221 1     => 5 3478 76 30195
    >py bla.py 221 2     => 5 5916 76 5759398
    >py bla.py 221 3     => 5 8200 76 55348
    >py bla.py 222 1     => 5 3478 76 30195
    >py bla.py 222 2     => 5 5916 76 5782877
    >py bla.py 222 3     => 5 8200 76 55399
    >py bla.py 223 1     => 5 3478 76 30195
    >py bla.py 223 2     => 5 5916 76 5806462
    >py bla.py 223 3     => 5 8200 76 55450
    >py bla.py 224 1     => 5 3478 76 30195
    >py bla.py 224 2     => 5 5916 72 5829381
    >py bla.py 224 3     => 5 8200 Segmentation fault
    >py bla.py 225 1     => 5 3478 76 30195
    >py bla.py 225 2     => 5 5916 76 5853950
    >py bla.py 225 3     => 5 8200 76 55552
    

    奇怪的东西(n**2,n) 在共享类型中有大量内存分配给它,而不是n**3(n,n,n)。但这不是重点。

    1. a mallocs 是一致的,仅略微依赖于维度,而完全不依赖于 n(对于测试的数字)。
    2. b mallocs 除了在形状 2 上高,n 也略有增加,但随着形状变化很大。
    3. 分段错误循环出现!在我的机器上,形状(n,n,n) 的内存分配接近一些n 依赖数字之前的sefault - 但对于n+1,我们又可以了。似乎在 160 左右约为 46k,在 224 左右约为 56k。

    我没有很好的解释,但是对n 的依赖让我认为分配需要很好地适应一些位结构,有时这会中断。

    我猜你的尺寸使用 225 会起作用 - 作为一种解决方法。

    【讨论】:

    • 这一定是numpy 中的实际错误(不太可能,因为np.ctypeslib 中的代码在mac 和linux 上是相同的)或multiprocessing
    • @kebanus 感谢您进行所有这些检查。我刚刚将我的 Ubuntu 从 16.04 升级到 18.04,但同样的段错误仍然存​​在(并且通过 tel 的解决方法可以避免)。所以,我想我会创建一个 Python 错误报告。
    猜你喜欢
    • 2019-05-11
    • 2016-01-07
    • 1970-01-01
    • 1970-01-01
    • 2011-11-24
    • 1970-01-01
    • 2012-10-31
    • 1970-01-01
    • 2014-04-09
    相关资源
    最近更新 更多