Как исправить проблему с многопроцессорным использованием python matplotlib savefig()?
Я хочу ускорить matplotlib.savefig() для многих фигур с помощью многопроцессорного модуля, и пытаюсь сравнить производительность между параллелью и последовательностью.
Ниже приведены коды:
# -*- coding: utf-8 -*-
"""
Compare the time of matplotlib savefig() in parallel and sequence
"""
import numpy as np
import matplotlib.pyplot as plt
import multiprocessing
import time
def gen_fig_list(n):
''' generate a list to contain n demo scatter figure object '''
plt.ioff()
fig_list = []
for i in range(n):
plt.figure();
dt = np.random.randn(5, 4);
fig = plt.scatter(dt[:,0], dt[:,1], s=abs(dt[:,2]*1000), c=abs(dt[:,3]*100)).get_figure()
fig.FM_figname = "img"+str(i)
fig_list.append(fig)
plt.ion()
return fig_list
def savefig_worker(fig, img_type, folder):
file_name = folder+"\\"+fig.FM_figname+"."+img_type
fig.savefig(file_name, format=img_type, dpi=fig.dpi)
return file_name
def parallel_savefig(fig_list, folder):
proclist = []
for fig in fig_list:
print fig.FM_figname,
p = multiprocessing.Process(target=savefig_worker, args=(fig, 'png', folder)) # cause error
proclist.append(p)
p.start()
for i in proclist:
i.join()
if __name__ == '__main__':
folder_1, folder_2 = 'Z:\\A1', 'Z:\\A2'
fig_list = gen_fig_list(10)
t1 = time.time()
parallel_savefig(fig_list,folder_1)
t2 = time.time()
print '\nMulprocessing time : %0.3f'%((t2-t1))
t3 = time.time()
for fig in fig_list:
savefig_worker(fig, 'png', folder_2)
t4 = time.time()
print 'Non_Mulprocessing time: %0.3f'%((t4-t3))
И я встречаю проблему "This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information."
ошибка вызвана p = multiprocessing.Process(target=savefig_worker, args=(fig, 'png', folder))
,
Зачем? И как это решить?
(Windows XP + Python: 2.6.1 + Numpy: 1.6.2 + Matplotlib: 1.2.0)
РЕДАКТИРОВАТЬ: (добавить сообщение об ошибке на Python 2.7.3)
При запуске на IDLE Python 2.7.3 ниже выдается сообщение об ошибке:
>>>
img0
Traceback (most recent call last):
File "C:\Documents and Settings\Administrator\desktop\mulsavefig_pilot.py", line 61, in <module>
proc.start()
File "d:\Python27\lib\multiprocessing\process.py", line 130, in start
File "d:\Python27\lib\pickle.py", line 286, in save
f(self, obj) # Call unbound method with explicit self
File "d:\Python27\lib\pickle.py", line 748, in save_global
(obj, module, name))
PicklingError: Can't pickle <function notify_axes_change at 0x029F5030>: it's not found as matplotlib.backends.backend_qt4.notify_axes_change
РЕДАКТИРОВАТЬ: (Моя демонстрация решения)
вдохновленный Matplotlib: одновременная прорисовка в нескольких потоках
# -*- coding: utf-8 -*-
"""
Compare the time of matplotlib savefig() in parallel and sequence
"""
import numpy as np
import matplotlib.pyplot as plt
import multiprocessing
import time
def gen_data(fig_qty, bubble_qty):
''' generate data for fig drawing '''
dt = np.random.randn(fig_qty, bubble_qty, 4)
return dt
def parallel_savefig(draw_data, folder):
''' prepare data and pass to worker '''
pool = multiprocessing.Pool()
fig_qty = len(draw_data)
fig_para = zip(range(fig_qty), draw_data, [folder]*fig_qty)
pool.map(fig_draw_save_worker, fig_para)
return None
def fig_draw_save_worker(args):
seq, dt, folder = args
plt.figure()
fig = plt.scatter(dt[:,0], dt[:,1], s=abs(dt[:,2]*1000), c=abs(dt[:,3]*100), alpha=0.7).get_figure()
plt.title('Plot of a scatter of %i' % seq)
fig.savefig(folder+"\\"+'fig_%02i.png' % seq)
plt.close()
return None
if __name__ == '__main__':
folder_1, folder_2 = 'A1', 'A2'
fig_qty, bubble_qty = 500, 100
draw_data = gen_data(fig_qty, bubble_qty)
print 'Mulprocessing ... ',
t1 = time.time()
parallel_savefig(draw_data, folder_1)
t2 = time.time()
print 'Time : %0.3f'%((t2-t1))
print 'Non_Mulprocessing .. ',
t3 = time.time()
for para in zip(range(fig_qty), draw_data, [folder_2]*fig_qty):
fig_draw_save_worker(para)
t4 = time.time()
print 'Time : %0.3f'%((t4-t3))
print 'Speed Up: %0.1fx'%(((t4-t3)/(t2-t1)))
2 ответа
На самом деле, это не ошибка, а скорее ограничение.
Объяснение в последней строке сообщения об ошибке:
PicklingError: Can't pickle <function notify_axes_change at 0x029F5030>: it's not found as matplotlib.backends.backend_qt4.notify_axes_change
Это говорит вам о том, что элементы объектов фигуры не могут быть замаринованы, вот как MultiProcess
передает данные между процессами. Объекты протравливаются в основных процессах, доставляются в виде маринованных огурцов, а затем перестраиваются на другой стороне. Даже если вы исправили именно эту проблему (возможно, с помощью другого бэкэнда, или удалили нарушающую функцию (которая может сломать вещи другими способами)), я почти уверен, что есть основные части Figure
, Axes
, или же Canvas
объекты, которые нельзя мариновать.
Как указывает @bigbug, пример того, как обойти это ограничение, Matplotlib: одновременное построение графика в нескольких потоках. Основная идея состоит в том, что вы выталкиваете всю подпрограмму печати в подпроцесс, поэтому вы только numpy
может быть, возможно, некоторая информация о конфигурации через границы процесса.
Вы можете попробовать переместить весь код matplotlib (включая импорт) в функцию.
Убедитесь, что у вас нет импорта matplotlib или импорта matplotlib.pyplot как plt в верхней части кода.
создайте функцию, которая выполняет все операции с matplotlib, включая импорт.
Пример:
import numpy as np
from multiprocessing import pool
def graphing_function(graph_data):
import matplotlib.pyplot as plt
plt.figure()
plt.hist(graph_data.data)
plt.savefig(graph_data.filename)
plt.close()
return
pool = Pool(4)
pool.map(graphing_function, data_list)