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

Dav*_*man 7 python shared-memory multiprocessing

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

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
Run Code Online (Sandbox Code Playgroud)

基本上我想要在多个进程之间共享一个numpy数组,因为它很大且只读.这种方法效果很好,不需要额外的副本,并且过程的实际计算时间也很好.但是创建共享阵列的开销是巨大的.

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

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

我正在使用Windows 7 x64系统.

Sha*_*ger 7

由于第二个链接中给出的原因,这很慢,并且解决方案实际上非常简单:绕过(慢)RawArray切片分配代码,在这种情况下,从源数组一次无效地读取一个原始C值以创建Python对象,然后将其直接转换回原始C以便存储在共享数组中,然后丢弃临时Python对象,并重复1e8次数.

但你不需要这样做; 像大多数C级事物一样,RawArray实现缓冲协议,这意味着你可以将它转换为一个memoryview底层原始内存的视图,它以类似C的方式实现大多数操作,如果可能的话使用原始内存操作.所以不要这样做:

# 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
Run Code Online (Sandbox Code Playgroud)

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!!!
Run Code Online (Sandbox Code Playgroud)

注意,后者的时间是毫秒,而不是秒; 使用memoryview包装进行复制以执行原始内存传输所需的时间不到1%,RawArray默认情况下采用模式化方法!

  • 请注意,在执行“原始”数据复制时,元素大小必须匹配;如果他们不这样做,你会从`memoryview` 得到一个`TypeError` 抱怨“不匹配的项目大小”;如果对象不能以匹配的大小创建,那么在不同的 `numpy` 数组类型之间转换通常相当便宜,所以在 64 位系统上,如果 `temp` 由 `ctypes.uint`(4 个字节)组成大多数系统)并且你有一个类型为 `np.uint` 的 `numpy` 数组要分配(64 位上的 8 个字节),你可以只使用 `memoryview(temp)[:] = np.array(toobigarray, dtype=np .uint32)` 先转换,然后是 `memcpy`。 (2认同)
  • 很好,谢谢!我遇到的一个问题是尝试此方法会引发错误:`NotImplementedError: memoryview: unsupported format <H`。这是 Windows 特定的错误吗?有没有简单的方法来解决它? (2认同)
  • @DavidHoffman:看起来在Python 3上,`memoryview`对于'temp`在小端机器上显然是小端字节顺序(`<H`)很挑剔,而`numpy``数组`是本机字节顺序(`H `,或等效地,`@ H`).在这种情况下,它们是相同的,所以你可以先通过强制转换来关闭`memoryview`:`memoryview(temp).cast('B').cast('H')[:] = np.arange(1e8 ,dtype = np.uint16)`.这如果事实证明,大小不匹配,您可能会检查`memoryview(临时).format.lstrip( '@ = <>!')== memoryview(np.arange(D型= np.uint16))_will_隐藏错误.format.lstrip('@ = <>!')`确定. (2认同)

Ada*_* S. 5

只需在共享数组周围放置一个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)
Run Code Online (Sandbox Code Playgroud)

然后时间:

%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
Run Code Online (Sandbox Code Playgroud)

无需弄清楚如何强制转换memoryview(就像我在python3 Ubuntu 16中所必须的)并弄乱了重塑(如果x有更大的尺寸,因为cast()变平了)。并用于sh_np.dtype.name像任何numpy数组一样仔细检查数据类型。:)