使用Pipe在进程之间传输Python对象时的字节限制?

时间:2013-05-15 23:01:05

标签: python struct parallel-processing multiprocessing

我有一个在64位Linux(内核)上运行的自定义模拟器(用于生物学) 版本2.6.28.4)使用64位Python 3.3.0 CPython解释器的机器。

因为模拟器依赖于许多独立实验来获得有效结果, 我建立了并行处理运行实验。之间的沟通 线程主要发生在具有托管的生产者 - 消费者模式下 multiprocessing QueueŠ (doc)。 架构的破败如下:

  • 处理产生和管理Process以及各种Queue
  • 的主流程
  • 进行模拟的N个工作进程
  • 1个结果消费者流程,它消耗模拟结果并对结果进行分类和分析

主进程和工作进程通过输入Queue进行通信。 同样,工作进程将其结果放在输出Queue中 结果使用者进程使用来自的项目。最后的ResultConsumer 对象通过multiprocessing Pipe传递 (doc) 回到主过程。

一切正常,直到它试图将ResultConsumer对象传递回去 主进程通过Pipe

Traceback (most recent call last):
  File "/home/cmccorma/.local/lib/python3.3/multiprocessing/process.py", line 258, in _bootstrap
    self.run()
  File "/home/cmccorma/.local/lib/python3.3/multiprocessing/process.py", line 95, in run
    self._target(*self._args, **self._kwargs)
  File "DomainArchitectureGenerator.py", line 93, in ResultsConsumerHandler
    pipeConn.send(resCon)
  File "/home/cmccorma/.local/lib/python3.3/multiprocessing/connection.py", line 207, in send
    self._send_bytes(buf.getbuffer())
  File "/home/cmccorma/.local/lib/python3.3/multiprocessing/connection.py", line 394, in _send_bytes
    self._send(struct.pack("!i", n))
struct.error: 'i' format requires -2147483648 <= number <= 2147483647

我理解前两个跟踪(Process库中未处理的出口), 第三个是我发送ResultConsumer对象的代码行 Pipe到主进程。最后两条痕迹就是它的所在 有趣。 Pipe腌制发送给它并通过的任何对象 结果字节到另一端(匹配连接),在那里它被打开 在运行recv()时。 self._send_bytes(buf.getbuffer())正在尝试 发送pickle对象的字节。 self._send(struct.pack("!i", n))是 尝试打包一个长度为n的整数(network / big-endian)的结构, 其中n是作为参数传入的缓冲区的长度(struct 库处理Python值和表示为的C结构之间的转换 Python字符串,请参阅the doc)。

只有在尝试大量实验时才会出现此错误,例如10个实验 不会导致它,但1000将立即(所有其他参数不变)。我最好的 假设struct.error被抛出的假设就是字节数 试图被推下管道超过2 ^ 32-1(2147483647),或~2 GB。

所以我的问题是双重的:

  1. 我基本上只是struct.py调查我的调查 从_struct导入,我不知道它在哪里。

  2. 鉴于底层架构是全部,字节限制似乎是任意的 64位。那么,为什么我不能通过比这更大的东西?另外,如果我 无法改变这一点,这个问题是否有任何好的(阅读:简单)解决方法?

  3. 注意:我认为使用Queue代替Pipe不会解决问题, 因为我怀疑Queue使用类似的酸洗中间步骤。 编辑:正如abarnert的回答所指出的那样,这个说明是完全错误的。

1 个答案:

答案 0 :(得分:10)

  

由于struct.py基本上只是从_struct导入,我不知道我的调查了,我不知道它在哪里。

在CPython中,_struct是源代码树中_struct.c目录中Modules构建的C扩展模块。您可以在线找到代码here

每当foo.py执行import _foo时,它几乎总是一个C扩展模块,通常是从_foo.c构建的。如果你根本找不到foo.py,它可能是一个C扩展模块,由_foomodule.c构建。

即使您没有使用PyPy,也常常值得查看equivalent PyPy source。他们在纯Python中重新实现了几乎所有的扩展模块 - 对于其余的(包括这种情况),底层的“扩展语言”是RPython,而不是C。

但是,在这种情况下,除了文档中的内容之外,您无需了解struct的工作方式。


  

鉴于底层架构都是64位,字节限制似乎是任意的。

查看它正在调用的代码:

self._send(struct.pack("!i", n))

如果查看the documentation'i'格式字符明确表示“4字节C整数”,而不是“ssize_t是什么”。为此,您必须使用'n'。或者您可能希望明确使用长'q'

您可以monkeypatch multiprocessing使用struct.pack('!q', n)。或'!q'。或者以struct以外的某种方式编码长度。当然,这会破坏与非修补multiprocessing的兼容性,如果您尝试跨多台计算机或其他东西进行分布式处理,这可能会出现问题。但它应该很简单:

def _send_bytes(self, buf):
    # For wire compatibility with 3.2 and lower
    n = len(buf)
    self._send(struct.pack("!q", n)) # was !i
    # The condition is necessary to avoid "broken pipe" errors
    # when sending a 0-length buffer if the other end closed the pipe.
    if n > 0:
        self._send(buf)

def _recv_bytes(self, maxsize=None):
    buf = self._recv(8) # was 4
    size, = struct.unpack("!q", buf.getvalue()) # was !i
    if maxsize is not None and size > maxsize:
        return None
    return self._recv(size)

当然不能保证这种改变是充分的;你会想要阅读周围代码的其余部分并测试它的地狱。


  

注意:我怀疑使用Queue代替Pipe无法解决问题,因为我怀疑Queue使用类似的酸洗中间步骤。

嗯,问题与酸洗无关。 Pipe未使用pickle发送长度,而是使用struct。您可以验证pickle不会出现此问题:pickle.loads(pickle.dumps(1<<100)) == 1<<100将返回True

(在早期版本中,pickle 存在大型对象问题 - 例如,list 2G元素 - 这可能导致大约8倍的问题你现在正打的那个很高。但这已经被3.3修正了。)

与此同时......尝试看看是不是更快,而不是通过挖掘源来试图弄清楚它是否会起作用?


另外,你确定你真的想通过隐式酸洗来传递2GB的数据结构吗?

如果我做了一些缓慢而且需要大量内存的事情,我宁愿将其显式 - 例如,pickle转换为临时文件并发送路径或fd。 (如果您使用的是numpypandas或其他内容,请使用其二进制文件格式而不是pickle,但同样的想法。)

或者,更好的是,共享数据。是的,可变共享状态很糟糕......但共享不可变对象很好。无论你有2GB,你可以把它放在multiprocessing.Array中,或者把它放在ctypes数组或结构(数组或结构......)中,你可以通过{{1}分享它},或multiprocessing.sharedctypes它来自ctypes你双方file,还是......?有一些额外的代码来定义和分离结构,但是当这些好处可能很大时,值得尝试。


最后,当您认为在Python中发现了一个错误/明显缺失的功能/不合理的限制时,值得查看错误跟踪器。看起来issue 17560: problem using multiprocessing with really big objects?正好是您的问题,并且包含大量信息,包括建议的解决方法。