2016-06-08 2 views
4

Вот небольшой бенчмаркинга код, чтобы проиллюстрировать мой вопрос:Почему задания multiprocessing.sharedctypes настолько медленны?

import numpy as np 
import multiprocessing as mp 
# allocate memory 
%time temp = mp.RawArray(np.ctypeslib.ctypes.c_uint16, int(1e8)) 
Wall time: 46.8 ms 
# assign memory, very slow 
%time temp[:] = np.arange(1e8, dtype = np.uint16) 
Wall time: 10.3 s 
# equivalent numpy assignment, 100X faster 
%time a = np.arange(1e8, dtype = np.uint16) 
Wall time: 111 ms 

В основном я хочу NumPy массив, который будет разделен между несколькими процессами, потому что это большой и только для чтения. This method отлично работает, никаких дополнительных копий не производится, и фактическое время вычислений на процессах хорошее. Но накладные расходы , создавая, разделяемый массив огромен.

This postThis post Предлагаем некоторое представление о том, почему некоторые способы инициализации массива медленны (обратите внимание, что в приведенном выше примере я использую более быстрый метод). Но сообщение не описывает, как действительно повысить скорость до нулевой производительности.

Есть ли у кого-нибудь предложения по улучшению скорости? Может ли какой-нибудь код на Cython выделить массив?

Я работаю над системой Windows 7 x64.

ответ

4

Это медленно по причинам, изложенным в your second link, и раствор на самом деле довольно прост: Обход (медленный) RawArray кусочек кода присваивания, который в этом случае неэффективно чтение одного необработанного значения C в то время, из source для создания объекта Python, затем преобразует его обратно в исходный C для хранения в общем массиве, затем отбрасывает временный объект Python и повторяет 1e8 раз.

Но вам не нужно это делать; как и большинство вещей уровня C, RawArray реализует буферный протокол, что означает, что вы можете преобразовать его в memoryview, a view of the underlying raw memory that implements most operations in C-like ways, используя необработанные операции с памятью, если это возможно. Так что вместо того, чтобы делать:

# assign memory, very slow 
%time temp[:] = np.arange(1e8, dtype = np.uint16) 
Wall time: 9.75 s # Updated to what my machine took, for valid comparison 

использовать memoryview манипулировать им в качестве сырья байт-подобный объект и назначить этот путь (np.arange уже реализует протокол буфера, и оператор присваивания ломтика memoryview «s легко использует его):

# C-like memcpy effectively, very fast 
%time memoryview(temp)[:] = np.arange(1e8, dtype = np.uint16) 
Wall time: 74.4 ms # Takes 0.76% of original time!!! 

Обратите внимание, что время для последнего составляет миллисекунды, а не секунды; копирование с использованием memoryview обертывание для выполнения необработанных передач памяти занимает менее 1% времени, чтобы сделать это путем plodding RawArray делает это по умолчанию!

+1

Обратите внимание, что при выполнении «сырых» копий данных размеры элементов должны совпадать; Если они этого не сделают, вы получите 'TypeError' из' memoryview', жалующийся на «несоответствующие размеры элементов»; если объекты не могут быть созданы с соответствующими размерами для запуска, обычно довольно дешево конвертировать между различными типами массивов 'numpy', поэтому в 64-битной системе, если' temp' был сделан из 'ctypes.uint' (4 байта на большинство систем), и у вас был массив 'numpy' типа' np.uint' для назначения (8 байтов на 64 бит), вы могли бы просто использовать 'memoryview (temp) [:] = np.array (toobigarray, dtype = np .uint32) 'для преобразования сначала, а затем' memcpy'. – ShadowRanger

+0

Удивительный, спасибо! Одна из проблем, с которыми я сталкиваюсь, заключается в том, что при попытке этого метода возникает ошибка: 'NotImplementedError: memoryview: неподдерживаемый формат

+1

@DavidHoffman: Похоже, что на Python 3 'memoryview' походит на' temp', явно ограниченный порядком байтов ('!') == memoryview (np.arange (dtype = np.uint16)) .format.lstrip ('@ = <>!') ', чтобы быть уверенным. – ShadowRanger

0

В ms-windows при создании Process будет создан новый интерпретатор Python, который затем импортирует вашу программу в виде модуля. (Вот почему в ms-окнах вы должны создавать только Process и Pool из блока if __name__ is "__main__".) Это позволит воссоздать ваш массив, который должен занимать примерно то же время, что и при создании. См. programming guidelines, особенно в отношении метода запуска spawn, который должен использоваться в ms-окнах.

Возможно, лучший способ - создать массив numped с памятью, используя numpy.memmap. Запишите массив на диск в родительском процессе. (В ms-окнах это должно быть выполнено в блоке if __name__ is "__main__", поэтому его называют раз). Затем в функции target используйте numpy.memmap в режиме только для чтения, чтобы прочитать данные.

+0

Передача его конструктору 'Process' в качестве аргументов будет' pickle' его и отправить его ребенку для десериализации копии, он не получит «бесплатную» семантику «fork» для копирования на запись (fork) он не будет «унаследован»), даже если вы находитесь на поддерживающей ОС fork. Если вы находитесь в операционной системе 'fork' и поддерживаете другие ОС, вы должны инициализировать объект в глобальной переменной за некоторое время до того, как вы создадите« Процесс », и дети смогут получить к нему доступ как обычно. Вы не хотите, чтобы он определялся в функции 'main', потому что тогда он не виден вне' main', даже в дочерних процессах. – ShadowRanger

+0

Я сделал некоторое дополнительное чтение, а в ms-windows я считаю, что использование 'numpy.memmap' является лучшим решением. Отсюда и измененный ответ. –

+1

'memmap's будет работать, это реализация, которую [joblib] (https://pypi.python.org/pypi/joblib) использует для обмена данными между процессами. Но если вы регулярно получаете доступ к общим данным, это будет болезненно медленным, а ускорения минимальны в лучшем случае. –

1

Просто поместите Numpy массив вокруг общего массива:

import numpy as np 
import multiprocessing as mp 

sh = mp.RawArray('i', int(1e8)) 
x = np.arange(1e8, dtype=np.int32) 
sh_np = np.ctypeslib.as_array(sh) 

то время:

%time sh[:] = x 
CPU times: user 10.1 s, sys: 132 ms, total: 10.3 s 
Wall time: 10.2 s 

%time memoryview(sh).cast('B').cast('i')[:] = x 
CPU times: user 64 ms, sys: 132 ms, total: 196 ms 
Wall time: 196 ms 

%time sh_np[:] = x 
CPU times: user 92 ms, sys: 104 ms, total: 196 ms 
Wall time: 196 ms 

Нет необходимости, чтобы выяснить, как бросить memoryview (как я должен был в Python3 Ubuntu 16) и беспорядок с изменением формы (если x имеет больше размеров, так как cast() flattens). И используйте sh_np.dtype.name, чтобы дважды проверять типы данных, как и любой массив numpy. :)

+0

Если мне не хватает чего-то очевидного, вроде бы это не работает в Windows? –

Смежные вопросы