Python 大型3D阵列的高效numpy切片

Python 大型3D阵列的高效numpy切片,python,arrays,performance,numpy,Python,Arrays,Performance,Numpy,我有一个大的3D numpy数组lookup=np.random.rand((10001000))。它代表1000个分辨率的图像(10002000)。对于每一张图像,我都试图得到不同位置的值列表。我有位置数组,m=np.rand.rand(1000,2)*1000;m=m.astype('int') 我得到的是每个切片的值(见下面的示例代码) 这个操作的速度惊人地慢。我的笔记本电脑大约有20毫秒。我希望它能快1-2个数量级。有没有更好的方法来分割这种类型的numpy阵列?这种计算速度不是因为nu

我有一个大的3D numpy数组
lookup=np.random.rand((10001000))
。它代表1000个分辨率的图像(10002000)。对于每一张图像,我都试图得到不同位置的值列表。我有位置数组,
m=np.rand.rand(1000,2)*1000;m=m.astype('int')

我得到的是每个切片的值(见下面的示例代码)


这个操作的速度惊人地慢。我的笔记本电脑大约有20毫秒。我希望它能快1-2个数量级。有没有更好的方法来分割这种类型的numpy阵列?

这种计算速度不是因为numpy,而是因为您的硬件,实际上是在大多数现代硬件上。其速度慢的主要原因是随机RAM访问导致延迟限制计算

输入阵列很大,因此不能存储在CPU缓存中,只能存储在RAM中。 现代RAM可以有相当高的吞吐量,但每次提取都需要相当大的延迟(在最近的x86处理器上,每次随机提取大约80ns)。虽然随着时间的推移,新RAM设备的吞吐量往往会显著提高,但延迟的情况几乎没有。按顺序一次获取一个(8字节)双精度浮点数将导致吞吐量
size\u of\u double/RAM\u latency=8/80e-9≈ 95 MiB/s
。这只是现代主流RAM设备功能的一小部分(几十GiB/s)

为了解决这个问题,现代处理器可以一次提取几个内存块,并尝试使用预取单元和推测来预测RAM访问和提前加载数据。这对于可预测的访问模式(特别是连续负载)很有效,但对于代码中的随机访问模式则完全无效。现代处理器仍然能够在一个顺序代码上并行获取多个内存块,但这还不够,因此这种代码的速度足够快(代码的吞吐量约为400 MiB/s)。更不用说主流x86处理器系统地从RAM设备加载64字节的缓存线,而您只需要8字节

一种解决方案是并行化此操作。但是,由于缓存线的原因,这不是很有效(您只能获得最大吞吐量的10%以上)

另一种解决方案是转置输入数据,以便获取的内存块可以更连续。以下是一个例子:

transposedLookup=np.array(lookup.T)
%timeit transposedLookup[m[:,0],m[:,1],:]T
请注意,第一次换位将相当缓慢(主要是因为它尚未通过Numpy进行优化,但也因为访问模式),并且需要两倍的RAM。您可以使用来加速换位。如果输入矩阵是立方的,也可以将其转置到位。如果可以直接以转置形式生成数组,那就更好了

还要注意的是,第二个转置速度很快,因为它是惰性完成的,但即使是一个急切的转置也比原始代码快好几倍

以下是我的机器上的计时:

原始代码:14.1毫秒 渴望的颈部移位:2.6毫秒 懒散Numpy移位:0.6毫秒
编辑:请注意,同样的事情适用于
m

,这不是一个切片。这是一个奇特的索引。你对此无能为力。20毫秒没什么好担心的。很多可能是python对象的开销知道它叫花式索引很好,谢谢。这对我来说是一个很大的担忧,因为这被称为一个整体,这20毫秒变得非常重要。我需要一种更快的方法来检索数据。您是否a)有实际的时间问题,b)分析了代码并发现这是瓶颈?a)是的,b)也是的,我建议您不再使用python,或者至少不再使用当前的解决方案。你可以通过展示更多的背景来开始。哇,这真是令人印象深刻!第一个转置不是问题,因为我可以以正确的方式创建查找,第二个转置不是问题,因为我将获取输出的np.min(axis=0),并且可以只拾取开关轴。我已经进步了5-10倍。有没有一种方法可以构造
m
使这个查询更快?排序查找索引可能会help@MikeAzatov我认为转置
m
会更快,但差异并不显著,因为
m
在缓存中(如果在实践中它更大,可能会很有趣)。我还尝试了排序
m
,它稍微快一点(10%),但可能会使代码不太清晰/不易维护。我看不到与
m
相关的任何其他可能的改进。谢谢,@JérômeRichard,你认为排序
m
的最佳方法是什么,因为它有两个索引?@mikeazatov tout应该根据m[0]的值对m进行排序,但也要对transposedLookup进行重新排序,以便两者的转换相同。我不确定这是否值得努力。
%timeit lookup[:,m[:,1], m[:,0]]