[英]Byte limit when transferring Python objects between Processes using a Pipe?
我有一个使用64位Python 3.3.0 CPython解释器在64位Linux(内核版本2.6.28.4)机器上运行的自定义模拟器(用于生物学)。
因为模拟器依赖于许多独立实验来获得有效结果,所以我建立了并行处理来运行实验。 线程之间的通信主要发生在具有托管multiprocessing Queue
( doc )的生产者 - 消费者模式下。 该体系结构的破坏如下:
Process
es以及各种Queue
的主进程 主进程和工作进程通过输入Queue
进行通信。 类似地,工作进程将其结果放在输出Queue
中,结果使用者进程使用该Queue
中的项目。 最终的ResultConsumer对象通过multiprocessing Pipe
( doc )传递回主进程。
一切正常,直到它试图通过Pipe
将ResultConsumer对象传递回主进程:
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值和表示为Python字符串的C结构之间的转换,请参阅doc )。
只有在尝试大量实验时才会出现此错误,例如,10个实验不会导致它,但1000个将是有意义的(所有其他参数都是恒定的)。 到目前为止,为什么抛出struct.error
我最好的假设是,尝试按下管道的字节数超过2 ^ 32-1(2147483647),或者大约2 GB。
所以我的问题是双重的:
我因为struct.py
基本上只是从_struct
而陷入调查,我不知道它在哪里。
鉴于底层架构都是64位,字节限制似乎是任意的。 那么,为什么我不能通过比这更大的东西? 另外,如果我无法改变这个问题,那么这个问题是否有任何好的(阅读:简单)解决方法?
注意:我不认为使用Queue
代替Pipe
会解决问题,因为我怀疑Queue
使用类似的酸洗中间步骤。 编辑:正如abarnert的回答所指出的,这个说明是完全错误的。
我因为struct.py基本上只是从_struct导入而陷入调查,我不知道它在哪里。
在CPython中, _struct
是一个C扩展模块, _struct
是从源树中的Modules
目录中的_struct.c
构建的。 您可以在这里找到在线代码。
每当foo.py
执行import _foo
,它几乎总是一个C扩展模块,通常是从_foo.c
。 如果你根本找不到foo.py
,它可能是一个C扩展模块,由_foomodule.c
。
即使你没有使用PyPy,它也经常值得查看等效的PyPy源代码 。 他们重新实现纯Python中的几乎所有扩展模块 - 对于其余的(包括本例),底层的“扩展语言”是RPython,而不是C.
但是,在这种情况下,您不需要了解struct
如何工作超出文档中的内容。
鉴于底层架构都是64位,字节限制似乎是任意的。
看看它调用的代码:
self._send(struct.pack("!i", n))
如果查看文档 , '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
也存在大型物体的问题 - 例如,2G元素的list
- 这可能导致问题的规模大约是目前正在击中的那么高的8倍。但是已经修正了3.3。)
与此同时......尝试看看它并不是更快,而不是通过挖掘源来试图弄清楚它是否会起作用?
另外,你确定你真的想通过隐式酸洗来传递2GB的数据结构吗?
如果我做了一些缓慢而且需要内存的东西,我宁愿将其显式化 - 例如,pickle到tempfile并发送路径或fd。 (如果您正在使用numpy
或pandas
或其他东西,请使用其二进制文件格式而不是pickle
,但同样的想法。)
或者,更好的是,共享数据。 是的,可变共享状态很糟糕......但共享不可变对象很好。 无论你有2GB,你可以把它放在一个multiprocessing.Array
,或者把它放在一个ctypes
数组或结构(数组或结构......)中你可以通过multiprocessing.sharedctypes
共享,或者ctypes
它是一个你双面mmap
file
,还是......? 有一些额外的代码来定义和分离结构,但是当这些好处可能很大时,值得尝试。
最后,当您认为在Python中发现了一个错误/明显缺失的功能/不合理的限制时,值得查看错误跟踪器。 看起来像问题17560:使用多处理真正大对象的问题? 这正是你的问题,并有很多信息,包括建议的解决方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.