【问题标题】:Python multiprocessing - Debugging OSError: [Errno 12] Cannot allocate memoryPython 多处理 - 调试 OSError:[Errno 12] 无法分配内存
【发布时间】:2017-07-23 21:31:25
【问题描述】:

我正面临以下问题。我正在尝试并行化更新文件的函数,但由于OSError: [Errno 12] Cannot allocate memory,我无法启动Pool()。我已经开始在服务器上环顾四周,这不像我使用的是旧的、弱的/超出实际内存的。 见htop 此外,free -m 表明除了约 7GB 的交换内存外,我还有大量可用的 RAM: 而且我尝试使用的文件也没有那么大。我将在下面粘贴我的代码(和堆栈跟踪),大小如下:

使用的predictionmatrix 数据框占用大约80MB 根据pandasdataframe.memory_usage() 文件geo.geojson 为 2MB

我该如何调试呢?我可以检查什么以及如何检查?感谢您提供任何提示/技巧!

代码:

def parallelUpdateJSON(paramMatch, predictionmatrix, data):
    for feature in data['features']: 
        currentfeature = predictionmatrix[(predictionmatrix['SId']==feature['properties']['cellId']) & paramMatch]
        if (len(currentfeature) > 0):
            feature['properties'].update({"style": {"opacity": currentfeature.AllActivity.item()}})
        else:
            feature['properties'].update({"style": {"opacity": 0}})

def writeGeoJSON(weekdaytopredict, hourtopredict, predictionmatrix):
    with open('geo.geojson') as f:
        data = json.load(f)
    paramMatch = (predictionmatrix['Hour']==hourtopredict) & (predictionmatrix['Weekday']==weekdaytopredict)
    pool = Pool()
    func = partial(parallelUpdateJSON, paramMatch, predictionmatrix)
    pool.map(func, data)
    pool.close()
    pool.join()

    with open('output.geojson', 'w') as outfile:
        json.dump(data, outfile)

堆栈跟踪:

---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
<ipython-input-428-d6121ed2750b> in <module>()
----> 1 writeGeoJSON(6, 15, baseline)

<ipython-input-427-973b7a5a8acc> in writeGeoJSON(weekdaytopredict, hourtopredict, predictionmatrix)
     14     print("Start loop")
     15     paramMatch = (predictionmatrix['Hour']==hourtopredict) & (predictionmatrix['Weekday']==weekdaytopredict)
---> 16     pool = Pool(2)
     17     func = partial(parallelUpdateJSON, paramMatch, predictionmatrix)
     18     print(predictionmatrix.memory_usage())

/usr/lib/python3.5/multiprocessing/context.py in Pool(self, processes, initializer, initargs, maxtasksperchild)
    116         from .pool import Pool
    117         return Pool(processes, initializer, initargs, maxtasksperchild,
--> 118                     context=self.get_context())
    119 
    120     def RawValue(self, typecode_or_type, *args):

/usr/lib/python3.5/multiprocessing/pool.py in __init__(self, processes, initializer, initargs, maxtasksperchild, context)
    166         self._processes = processes
    167         self._pool = []
--> 168         self._repopulate_pool()
    169 
    170         self._worker_handler = threading.Thread(

/usr/lib/python3.5/multiprocessing/pool.py in _repopulate_pool(self)
    231             w.name = w.name.replace('Process', 'PoolWorker')
    232             w.daemon = True
--> 233             w.start()
    234             util.debug('added worker')
    235 

/usr/lib/python3.5/multiprocessing/process.py in start(self)
    103                'daemonic processes are not allowed to have children'
    104         _cleanup()
--> 105         self._popen = self._Popen(self)
    106         self._sentinel = self._popen.sentinel
    107         _children.add(self)

/usr/lib/python3.5/multiprocessing/context.py in _Popen(process_obj)
    265         def _Popen(process_obj):
    266             from .popen_fork import Popen
--> 267             return Popen(process_obj)
    268 
    269     class SpawnProcess(process.BaseProcess):

/usr/lib/python3.5/multiprocessing/popen_fork.py in __init__(self, process_obj)
     18         sys.stderr.flush()
     19         self.returncode = None
---> 20         self._launch(process_obj)
     21 
     22     def duplicate_for_child(self, fd):

/usr/lib/python3.5/multiprocessing/popen_fork.py in _launch(self, process_obj)
     65         code = 1
     66         parent_r, child_w = os.pipe()
---> 67         self.pid = os.fork()
     68         if self.pid == 0:
     69             try:

OSError: [Errno 12] Cannot allocate memory

更新

根据@robyschek 的解决方案,我已将代码更新为:

global g_predictionmatrix 

def worker_init(predictionmatrix):
    global g_predictionmatrix
    g_predictionmatrix = predictionmatrix    

def parallelUpdateJSON(paramMatch, data_item):
    for feature in data_item['features']: 
        currentfeature = predictionmatrix[(predictionmatrix['SId']==feature['properties']['cellId']) & paramMatch]
        if (len(currentfeature) > 0):
            feature['properties'].update({"style": {"opacity": currentfeature.AllActivity.item()}})
        else:
            feature['properties'].update({"style": {"opacity": 0}})

def use_the_pool(data, paramMatch, predictionmatrix):
    pool = Pool(initializer=worker_init, initargs=(predictionmatrix,))
    func = partial(parallelUpdateJSON, paramMatch)
    pool.map(func, data)
    pool.close()
    pool.join()


def writeGeoJSON(weekdaytopredict, hourtopredict, predictionmatrix):
    with open('geo.geojson') as f:
        data = json.load(f)
    paramMatch = (predictionmatrix['Hour']==hourtopredict) & (predictionmatrix['Weekday']==weekdaytopredict)
    use_the_pool(data, paramMatch, predictionmatrix)     
    with open('trentino-grid.geojson', 'w') as outfile:
        json.dump(data, outfile)

我仍然遇到同样的错误。另外,根据documentationmap() 应该将我的data 分成块,所以我认为它不应该复制我的 80MBs rownum 次。不过我可能是错的...... :) 另外,我注意到如果我使用较小的输入(~11MB 而不是 80MB),我不会收到错误消息。所以我想我正在尝试使用太多内存,但我无法想象它是如何从 80MB 变为 16GB 的 RAM 无法处理的。

【问题讨论】:

  • 对不起,我懒得看stacktrace,没有注意到错误发生在os.fork。另外,我查看了多处理源,发现我关于复制predictionmatrix 的理论仅与Pool.imap 和小chunksize 相关,Pool.map 默认不受影响。我已经删除了我的答案。

标签: python linux out-of-memory python-multiprocessing


【解决方案1】:

使用multiprocessing.Pool 时,启动进程的默认方式是forkfork 的问题是整个过程是重复的。 (see details here)。因此,如果您的主进程已经在使用大量内存,则此内存将被复制,达到此MemoryError。例如,如果您的主进程使用 2GB 的内存,而您使用 8 个子进程,则您需要在 RAM 中使用 18GB

您应该尝试使用不同的启动方法,例如'forkserver''spawn'

from multiprocessing import set_start_method, Pool
set_start_method('forkserver')

# You can then start your Pool without each process
# cloning your entire memory
pool = Pool()
func = partial(parallelUpdateJSON, paramMatch, predictionmatrix)
pool.map(func, data)

这些方法避免了复制 Process 的工作区,但由于您需要重新加载正在使用的模块,因此启动速度可能会稍慢。

【讨论】:

  • 谢谢,我会调查一下,但我认为对于有 16GB 可用 RAM 的系统来说,处理 100 MB(甚至 2 gig)应该不会太多。此外,pool = Pool() 方法是使用多处理库的方法,即使根据 Python 文档也是如此。
  • 我澄清了我的答案。这里的启动方法是要求多处理使用与fork 不同的方法启动子进程,这会导致MemoryError
  • 几乎所有当前使用的类 UNIX 操作系统在其内存管理中都有写时复制。这意味着相同的内存页面在进程之间共享​​>。只有当进程修改页面中的任何内容时,它才会获得私有副本。
  • 是的,但是由于os.fork 上发生错误,我没有看到 fork 因MemoryError 而失败的其他原因,并且会尝试使用其他 start_method 来查看这是否来自这里。
  • 似乎只有 Python3 支持 set_start_method。在 Python 2.7 中是否有任何替代方法可以使用?
【解决方案2】:

我们有过几次。根据我的系统管理员的说法,unix 中有一个“错误”,如果您的内存不足,或者您的进程达到最大文件描述符限制,则会引发相同的错误。

我们遇到了文件描述符泄漏,引发的错误是 [Errno 12] Cannot allocate memory#012OSError。

所以你应该看看你的脚本并仔细检查问题是否不是创建了太多的 FD

【讨论】:

  • 由于文件描述符是在with open(...) as fd 上下文中创建的,一旦退出上下文,Python 不应该自动关闭 FD 吗?我很困惑这将如何超过限制
猜你喜欢
  • 1970-01-01
  • 2010-11-24
  • 2013-08-01
  • 2015-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-16
  • 2019-08-23
相关资源
最近更新 更多