Python Numpy:为什么nparray [i] [j]比nparray [i,j]慢?

时间:2017-01-22 13:07:21

标签: python arrays numpy

我正在初始化一个numpy数组,然后在某个任意位置插入循环值;由于某种原因,似乎使用双括号进行索引的速度实际上是使用逗号表示法编制索引的两倍。

编辑:根据Mike的回答,我想了解多维索引如何作为单个操作实现,以及是否有使用第一个符号。

import numpy as np

x = np.array([[1, 2, 3], [2, 3, 4], [3, 4, 5]])

def access(arr):
    for i in range(1000):
        arr[1][2] = i

def access2(arr):
    for i in range(1000):
        arr[1,2] = i        

t1 = Timer('access(x)', 'from __main__ import access, x')
print(t1.timeit(number = 1000))

0.425940990448

t2 = Timer('access2(x)', 'from __main__ import access2, x')
print(t2.timeit(number = 1000))

0.217132806778

2 个答案:

答案 0 :(得分:5)

答案

此:

nparray[i][j] 

表示两个索引操作。

这基本上就是这样:

tmp = nparray[i]
tmp[j] 

因此,您创建了一个您不再需要的中间数组tmp。 这是额外的工作。

虽然这个:

nparray[i, j]

只是一个索引操作。 NumPy使用此方法更有效,因为它不需要在此创建任何中间数组。

如何运作

执行nparray[i, j]时会发生这种情况。 班级ndarray会覆盖特殊方法__getitem____setitem__。 例如:

>>> class A:
...     def __getitem__(self, indices):
...         print(indices)
...     def __setitem__(self, indices, value):
...         print(indices, value)
...
>>> a = A()
>>> a[1, 2]
(1, 2)
>>> a[1, 2] = 23
(1, 2) 23

您可以看到1, 2中的[1, 2]作为元组到达那里。 现在,NumPy可以在适当的情况下处理这些信息。 在我们的例子中做一些2D索引。

较慢方法的可能用例

使用此方法的次数并不多:

nparray[i][j] 

其中一个可能是当您使用2D结构的列表列表时(也出于某种原因)想要使用NumPy数组作为替代品。虽然速度较慢,但​​代码可以使用列表列表以及2D NumPy数组。

答案 1 :(得分:4)

让我们比较一下反汇编:

>>> dis.dis(access)
  2           0 SETUP_LOOP              34 (to 37)
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (1000)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                20 (to 36)
             16 STORE_FAST               1 (i)

#------------- differences start here -------------#
  3          19 LOAD_FAST                1 (i)
             22 LOAD_FAST                0 (arr)
             25 LOAD_CONST               2 (1)
             28 BINARY_SUBSCR       
             29 LOAD_CONST               3 (2)
             32 STORE_SUBSCR        
#-------------- differences end here --------------#
             33 JUMP_ABSOLUTE           13
        >>   36 POP_BLOCK           
        >>   37 LOAD_CONST               0 (None)
             40 RETURN_VALUE        

>>> dis.dis(access2)
  2           0 SETUP_LOOP              30 (to 33)
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (1000)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                16 (to 32)
             16 STORE_FAST               1 (i)

#------------- differences start here -------------#
  3          19 LOAD_FAST                1 (i)
             22 LOAD_FAST                0 (arr)
             25 LOAD_CONST               4 ((1, 2))
             28 STORE_SUBSCR        
#-------------- differences end here --------------#
             29 JUMP_ABSOLUTE           13
        >>   32 POP_BLOCK           
        >>   33 LOAD_CONST               0 (None)
             36 RETURN_VALUE        

由此,我们可以看到第一个版本涉及两个额外的操作(LOAD_CONST后跟BINARY_SUBSCR)。您的实验表明,通过元组执行单个下标查找会更加昂贵,就像第二个函数所做的那样。