了解numpy.reshape的性能行为

o2gm4chl  于 2023-10-19  发布在  其他
关注(0)|答案(1)|浏览(97)

我有一个问题,np.reshape是一个瓶颈。我想知道在什么情况下它是快的,什么时候它是慢的。下面的代码解释了我的问题:

import time
import numpy as np
n = 28

# perm = np.arange(n)[::-1] #very slow
# perm = (np.arange(n)+15)%n #still slow
perm = (np.arange(n)+1)%n #fast

test = np.arange(2**n, dtype = np.complex64)

start_time = time.time()
test = test.reshape(n*[2])
test = np.moveaxis(test, np.arange(n), perm)
test = test.reshape(2**n)
print(time.time()-start_time)

有人知道在哪里可以找到整形操作的C源代码吗?

编辑

它似乎取决于排列数组到恒等式之间的距离,即。

distance = np.sum(np.abs(perm-np.arange(n)))

我已经执行了一些基准测试,loglog(运行时)似乎与距离呈线性相关。benchmark plot

pzfprimi

pzfprimi1#

用你的n=28我得到的时间(与你的结果相似):

In [436]: %%timeit
     ...: test1 = test.reshape(n*[2])
     ...: test1 = np.moveaxis(test1, np.arange(n), perm1)
     ...: test1 = test1.reshape(2**n)
     ...: 
     ...: 
23 s ± 239 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [437]: %%timeit
     ...: test1 = test.reshape(n*[2])
     ...: test1 = np.moveaxis(test1, np.arange(n), perm2)
     ...: test1 = test1.reshape(2**n)
     ...: 
     ...: 
11.5 s ± 22.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [438]: %%timeit
     ...: test1 = test.reshape(n*[2])
     ...: test1 = np.moveaxis(test1, np.arange(n), perm3)
     ...: test1 = test1.reshape(2**n)
     ...: 
     ...: 
719 ms ± 28.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

3个排列顺序为:
反转的:

perm1
Out[428]: 
array([27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11,
       10,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0])

两块交换

perm2
Out[429]: 
array([15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,  0,  1,  2,  3,
        4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14], dtype=int32)

第一个轴移动到末端:

perm3
Out[430]: 
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17,
       18, 19, 20, 21, 22, 23, 24, 25, 26, 27,  0], dtype=int32)

最后一个reshape就是np.ravel()
所有情况下都会在moveaxis中产生view,然后在ravel中产生copy
4例病例的步幅为:

In [446]: test1 = test.reshape(n*[2])
     ...: np.array(test1.strides)
Out[446]: 
array([536870912, 268435456, 134217728,  67108864,  33554432,  16777216,
         8388608,   4194304,   2097152,   1048576,    524288,    262144,
          131072,     65536,     32768,     16384,      8192,      4096,
            2048,      1024,       512,       256,       128,        64,
              32,        16,         8,         4])

In [447]: test1 = test.reshape(n*[2])
     ...: np.array(np.moveaxis(test1, np.arange(n), perm1).strides)
Out[447]: 
array([        4,         8,        16,        32,        64,       128,
             256,       512,      1024,      2048,      4096,      8192,
           16384,     32768,     65536,    131072,    262144,    524288,
         1048576,   2097152,   4194304,   8388608,  16777216,  33554432,
        67108864, 134217728, 268435456, 536870912])

In [448]: test1 = test.reshape(n*[2])
     ...: np.array(np.moveaxis(test1, np.arange(n), perm2).strides)
Out[448]: 
array([    65536,     32768,     16384,      8192,      4096,      2048,
            1024,       512,       256,       128,        64,        32,
              16,         8,         4, 536870912, 268435456, 134217728,
        67108864,  33554432,  16777216,   8388608,   4194304,   2097152,
         1048576,    524288,    262144,    131072])

In [449]: test1 = test.reshape(n*[2])
     ...: np.array(np.moveaxis(test1, np.arange(n), perm3).strides)
Out[449]: 
array([        4, 536870912, 268435456, 134217728,  67108864,  33554432,
        16777216,   8388608,   4194304,   2097152,   1048576,    524288,
          262144,    131072,     65536,     32768,     16384,      8192,
            4096,      2048,      1024,       512,       256,       128,
              64,        32,        16,         8])

这些都是相同的步长,但只是重新排序的预测烫发。

解析

reshape/ravel必须采用重新排序的版本,并按照步幅指定的顺序遍历值,首先迭代最小的值。其他对处理器缓存等有更好理解的人可能能够更好地解释这一点,但是不同的顺序可能需要不同的时间。该大小足够大,以至于内存访问时间(包括cash和页面)可能会有所不同。
总而言之,我不认为这是np.reshape做什么的问题,而更多的是以各种顺序遍历内存需要多长时间的问题。
正如我的评论所示,我首先认为最大的区别是在视图与副本。但现在我发现所有案子都有副本。只是有些维度混合需要比其他维度更长的时间来遍历。您必须比reshape函数本身更深入地挖掘才能理解发生了什么。它涉及多维数组的最内部工作,特别是strides的含义和使用。
如果你不用这么大的n,我的演示会更紧凑。我首先尝试了n=5,但ravel步骤比moveaxis步骤花费的时间更少。我怀疑有一个中等大小的烫发顺序没有太大的区别。忽略缓存和分页问题,跨越式多维数组应该能够在大约相等的时间内访问任何顺序的维度-例如,如果ravel时间是毫秒级。

相关问题