cProfile вызывает ошибку выбора при запуске многопроцессорного кода Python - PullRequest
0 голосов
/ 21 декабря 2018

У меня есть скрипт Python, который работает нормально, когда я запускаю его нормально:

$ python script.py <options>

Я пытаюсь профилировать код с помощью модуля cProfile:

$ python -m cProfile -o script.prof script.py <options>

Когда я запускаю вышеупомянутую команду, я получаю сообщение об ошибке, связанную с невозможностью выбора функции:

Traceback (most recent call last):
  File "scripts/process_grid.py", line 1500, in <module>
    _compute_write_index(kwrgs)
  File "scripts/process_grid.py", line 626, in _compute_write_index
    args,
  File "scripts/process_grid.py", line 1034, in _parallel_process
    pool.map(_apply_along_axis_palmers, chunk_params)
  File "/home/james/miniconda3/envs/climate/lib/python3.6/multiprocessing/pool.py", line 266, in map
    return self._map_async(func, iterable, mapstar, chunksize).get()
  File "/home/james/miniconda3/envs/climate/lib/python3.6/multiprocessing/pool.py", line 644, in get
    raise self._value
  File "/home/james/miniconda3/envs/climate/lib/python3.6/multiprocessing/pool.py", line 424, in _handle_tasks
    put(task)
  File "/home/james/miniconda3/envs/climate/lib/python3.6/multiprocessing/connection.py", line 206, in send
    self._send_bytes(_ForkingPickler.dumps(obj))
  File "/home/james/miniconda3/envs/climate/lib/python3.6/multiprocessing/reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
_pickle.PicklingError: Can't pickle <function _apply_along_axis_palmers at 0x7fe05a540b70>: attribute lookup _apply_along_axis_palmers on __main__ failed

В коде используется многопроцессорная обработка, и я предполагаю, что именно здесь происходит травление

Код в игре здесь, на GitHub .

По сути, я отображаю функцию и соответствующий словарь аргументов в пуле процессов:

pool.map(_apply_along_axis_palmers, chunk_params)

Функция _apply_along_axis_palmers, насколько я знаю, «поддается отбору» в том смысле, что она определена на верхнем уровне модуля.Опять же, эта ошибка не возникает при запуске вне контекста cProfile, так что, может быть, это добавляет дополнительные ограничения для травления?

Может кто-нибудь прокомментировать, почему это может происходить, и / или как я могу исправить проблему

1 Ответ

0 голосов
/ 22 декабря 2018

Проблема, с которой вы столкнулись, заключается в том, что при использовании -mcProfile модуль __main__ равен cProfile (фактическая точка входа в код), а не ваш сценарий.cProfile пытается исправить это, убедившись, что при запуске вашего скрипта он видит __name__ как "__main__", поэтому он знает, что он запускается как скрипт, а не импортируется как модуль, но sys.modules['__main__'] остается модулем cProfile.

Проблема в том, что pickle обрабатывает функции выбора, просто выбирая их квалифицированное имя (плюс несколько шаблонов, чтобы сказать, что это функция в первую очередь).И чтобы удостовериться, что он выживет в оба конца, он всегда проверяет, можно ли найти квалифицированное имя в sys.modules.Поэтому, когда вы делаете pickle.dumps(_apply_along_axis_palmers) (явно или неявно в данном случае, передавая его в качестве функции отображения), где _apply_along_axis_palmers определено в вашем основном скрипте, он дважды проверяет, существует ли sys.modules['__main__']._apply_along_axis_palmers.Но это не так, потому что cProfile._apply_along_axis_palmers не существует.

Я не знаю хорошего решения для этого.Лучшее, что я могу придумать, - это вручную исправить sys.modules, чтобы правильно выставить ваш модуль и его содержимое.Я не проверял это полностью, поэтому возможно, что будут некоторые причуды, но решение, которое я нашел, состоит в том, чтобы изменить модуль с именем mymodule.py вида:

# imports...
# function/class/global defs...

if __name__ == '__main__':
    main()  # Or series of statements

на:

# imports...
import sys
# function/class/global defs...

if __name__ == '__main__':
    import cProfile
    # if check avoids hackery when not profiling
    # Optional; hackery *seems* to work fine even when not profiling, it's just wasteful
    if sys.modules['__main__'].__file__ == cProfile.__file__:
        import mymodule  # Imports you again (does *not* use cache or execute as __main__)
        globals().update(vars(mymodule))  # Replaces current contents with newly imported stuff
        sys.modules['__main__'] = mymodule  # Ensures pickle lookups on __main__ find matching version
    main()  # Or series of statements

С этого момента, sys.modules['__main__'] относится к вашему собственному модулю, а не cProfile, так что, похоже, все работает.cProfile все еще работает, несмотря на это, и травление находит ваши функции, как и ожидалось.Только реальная стоимость реимпортирует ваш модуль, но если вы выполняете достаточно реальной работы, стоимость реимпорта должна быть довольно небольшой.

...