numpy 内存映射上随机切片的效率

Att*_*k68 2 python numpy numpy-memmap

我有一个 20GB、100k x 100k 'float16' 2D 数组作为数据文件。我将其加载到内存中,如下所示:

\n\n
fp_read = np.memmap(filename, dtype='float16', mode='r', shape=(100000, 100000))\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后我尝试从中读取切片。我需要采取的垂直切片实际上是随机的,但性能非常差,或者我做错了什么?

\n\n
\n\n

分析:

\n\n

我与其他形式的横截面切片进行了比较,尽管我不知道为什么应该这样,但它要好得多:

\n\n
%timeit fp_read[:,17000:17005]    # slice 5 consecutive cols\n1.64 \xc2\xb5s \xc2\xb1 16.4 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 1000000 loops each)\n\n%timeit fp_read[:,11000:11050:10]\n1.67 \xc2\xb5s \xc2\xb1 21 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 1000000 loops each)\n\n%timeit fp_read[:,5000:6000:200]\n1.66 \xc2\xb5s \xc2\xb1 27.3 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 1000000 loops each)\n\n%timeit fp_read[:,0:100000:20000]    # slice 5 disperse cols\n1.69 \xc2\xb5s \xc2\xb1 14.7 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 1000000 loops each)\n\n%timeit fp_read[:,[1,1001,27009,81008,99100]]     # slice 5 rand cols\n32.4 ms \xc2\xb1 10.9 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 100 loops each)\n\na = np.arange(100000); b = np.array([1,1001,27009,81008,99100])\n%timeit fp_read[np.ix_(a,b)]\n18 ms \xc2\xb1 142 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 100 loops each)\n
Run Code Online (Sandbox Code Playgroud)\n\n

即使这些 timeit 函数也无法准确捕获性能下降情况,因为:

\n\n
import time\na = np.arange(100000)\ncols = np.arange(100000)\nnp.random.shuffle(cols)\ncols = np.sort(cols[:5])\nt = time.time()\narr = fp_read[np.ix_(a,cols)]\nprint('Actually took: {} seconds'.format(time.time() - t))\nActually took: 24.5 seconds\n
Run Code Online (Sandbox Code Playgroud)\n\n

和....相比:

\n\n
t = time.time()\narr = fp_read[:,0:100000:20000]\nprint('Actually took: {} seconds'.format(time.time() - t))\nActually took 0.00024 seconds\n
Run Code Online (Sandbox Code Playgroud)\n

use*_*814 8

性能差异可以通过“基本切片和索引”与“高级索引”的一个关键差异来解释,请参阅这些文档。这里的关键行是

高级索引始终返回数据的副本(与返回视图的基本切片相反

副本有多痛,对比一下就能fp_read[:,5000:6000:200]看出fp_read[:,5000:6000:200].copy()

尽管制作数组副本总是比制作新视图慢,但对于内存映射来说尤其糟糕:

  1. 从磁盘读取相对较慢。需要从磁盘读取数据以进行(内存中)复制,而视图根本不需要读取任何数据!只是使用内存缓冲区的新偏移量和步长(步幅)参数创建了一个新的 ndarray 对象。
  2. 数据的内存布局是行优先顺序(与列优先顺序,请参阅维基百科)。对于访问随机列,这意味着必须从磁盘读取每个单个数据值的扇区。与连续访问相比,在连续访问中,每 256 个值仅读取一个扇区(假设是 float16 和 512 字节扇区)。对于内存映射 io,这种效果甚至更糟,因为数据是在 4kB 的块(内存页)中读取的,即 8 x 512 字节扇区。

现在我们也可以理解为什么 timeit 结果并不真正具有代表性:文件的特定部分由操作系统缓存在内存中。