为什么 multiprocessing.sharedctypes 赋值这么慢?
Why are multiprocessing.sharedctypes assignments so slow?
这里有一些基准代码来说明我的问题:
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 效果很好,没有制作额外的副本,并且进程的实际计算时间很好。但是 创建 共享数组的开销是巨大的。
提供了一些关于为什么某些初始化数组的方法很慢的深刻见解(请注意,在上面的示例中我使用了更快的方法)。但是 post 并没有真正描述如何真正将速度提高到像 numpy 一样的性能。
有没有人对如何提高速度有什么建议?一些 cython 代码对分配数组有意义吗?
我正在开发 Windows 7 x64 系统。
在 ms-windows 上,当您创建 Process
时,将生成一个新的 Python 解释器,然后 将您的程序导入 一个模块。 (这就是为什么在 ms-windows 上你应该只在 if __name__ is "__main__"
块中创建 Process
和 Pool
。)这将重新创建你的数组,它应该花费大约相同的时间创造它最初的时间。请参阅 programming guidelines,特别是关于必须在 ms-windows.
上使用的 spawn
启动方法
所以可能更好的方法是使用 numpy.memmap
创建一个内存映射的 numpy 数组。在父进程中将数组写入磁盘。 (在 ms-windows 上,这个 必须 在 if __name__ is "__main__"
块中完成,所以它只被称为 一次 )。然后在target
函数中使用numpy.memmap
以只读模式读取数据。
由于 中给出的原因,这很慢,解决方案实际上非常简单:绕过(慢)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
会这样做!
只需在共享数组周围放置一个 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 数组一样。 :)
这里有一些基准代码来说明我的问题:
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 效果很好,没有制作额外的副本,并且进程的实际计算时间很好。但是 创建 共享数组的开销是巨大的。
有没有人对如何提高速度有什么建议?一些 cython 代码对分配数组有意义吗?
我正在开发 Windows 7 x64 系统。
在 ms-windows 上,当您创建 Process
时,将生成一个新的 Python 解释器,然后 将您的程序导入 一个模块。 (这就是为什么在 ms-windows 上你应该只在 if __name__ is "__main__"
块中创建 Process
和 Pool
。)这将重新创建你的数组,它应该花费大约相同的时间创造它最初的时间。请参阅 programming guidelines,特别是关于必须在 ms-windows.
spawn
启动方法
所以可能更好的方法是使用 numpy.memmap
创建一个内存映射的 numpy 数组。在父进程中将数组写入磁盘。 (在 ms-windows 上,这个 必须 在 if __name__ is "__main__"
块中完成,所以它只被称为 一次 )。然后在target
函数中使用numpy.memmap
以只读模式读取数据。
由于 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
会这样做!
只需在共享数组周围放置一个 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 数组一样。 :)