我有一个在64位Linux(内核)上运行的自定义模拟器(用于生物学) 版本2.6.28.4)使用64位Python 3.3.0 CPython解释器的机器。
因为模拟器依赖于许多独立实验来获得有效结果,
我建立了并行处理运行实验。之间的沟通
线程主要发生在具有托管的生产者 - 消费者模式下
multiprocessing Queue
Š
(doc)。
架构的破败如下:
Process
以及各种Queue
主进程和工作进程通过输入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。
所以我的问题是双重的:
我基本上只是struct.py
调查我的调查
从_struct
导入,我不知道它在哪里。
鉴于底层架构是全部,字节限制似乎是任意的 64位。那么,为什么我不能通过比这更大的东西?另外,如果我 无法改变这一点,这个问题是否有任何好的(阅读:简单)解决方法?
注意:我认为使用Queue
代替Pipe
不会解决问题,
因为我怀疑Queue
使用类似的酸洗中间步骤。 编辑:正如abarnert的回答所指出的那样,这个说明是完全错误的。
答案 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。 (如果您使用的是numpy
或pandas
或其他内容,请使用其二进制文件格式而不是pickle
,但同样的想法。)
或者,更好的是,共享数据。是的,可变共享状态很糟糕......但共享不可变对象很好。无论你有2GB,你可以把它放在multiprocessing.Array
中,或者把它放在ctypes
数组或结构(数组或结构......)中,你可以通过{{1}分享它},或multiprocessing.sharedctypes
它来自ctypes
你双方file
,还是......?有一些额外的代码来定义和分离结构,但是当这些好处可能很大时,值得尝试。
最后,当您认为在Python中发现了一个错误/明显缺失的功能/不合理的限制时,值得查看错误跟踪器。看起来issue 17560: problem using multiprocessing with really big objects?正好是您的问题,并且包含大量信息,包括建议的解决方法。