为什么multiprocessing.sharedctypes赋值如此之慢?

时间:2016-06-08 14:52:16

标签: python multiprocessing shared-memory

这里有一个基准测试代码来说明我的问题:

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 post提供了一些很好的见解,说明为什么某些初始化数组的方法很慢(请注意,在上面的示例中我使用的是更快的方法)。但这篇文章并没有真正描述如何真正提高速度,使其像性能一样难以捉摸。

有没有人对如何提高速度有任何建议?一些cython代码是否有意义分配数组?

我正在使用Windows 7 x64系统。

3 个答案:

答案 0 :(得分:7)

由于your second link中给出的原因,这很慢,解决方案实际上很简单:绕过(慢)RawArray切片分配代码,在这种情况下从源数组一次低效地读取一个原始C值以创建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的切片分配操作符无缝地实现使用它):

# 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%,默认情况下RawArray执行它的方式很简单!

答案 1 :(得分:4)

只需在共享数组周围放置一个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

无需弄清楚如何构建内存视图(就像我在python3 Ubuntu 16中所做的那样)并且重新整形(如果x有更多维度,因为cast()会变平)。并使用sh_np.dtype.name来检查数据类型,就像任何numpy数组一样。 :)

答案 2 :(得分:0)

在ms-windows上创建Process时,将生成一个新的Python解释器,然后程序作为模块导入。 (这就是在ms-windows上你应该只在Process块中创建Poolif __name__ is "__main__"的原因。)这将重新创建你的数组,这应该与创建它的时间大致相同。原来做到了。请参阅programming guidelines,尤其是关于必须在ms-windows上使用的spawn启动方法。

所以可能更好的方法是使用numpy.memmap创建一个内存映射的numpy数组。将数组写入父进程中的磁盘。 (在ms-windows上,此必须if __name__ is "__main__"块中完成,因此它只被称为一次)。然后在target函数中以只读模式使用numpy.memmap来读取数据。