Python Numpy单个元素访问速度比列表慢

Python Numpy单个元素访问速度比列表慢,python,arrays,list,numpy,Python,Arrays,List,Numpy,我刚开始使用Numpy,并注意到在Numpy数组中迭代每个元素的速度比使用列表进行相同操作要慢4倍左右。我现在知道这违背了Numpy的目的,如果可能的话,我应该将函数矢量化。我的问题是为什么它慢了4倍。这似乎是一个相当大的数目 我使用%timeit import numpy as np b = np.eye(1000) a = b.tolist() %timeit b[100][100] #1000000 loops, best of 3: 692 ns per loop %timeit a[

我刚开始使用Numpy,并注意到在Numpy数组中迭代每个元素的速度比使用列表进行相同操作要慢4倍左右。我现在知道这违背了Numpy的目的,如果可能的话,我应该将函数矢量化。我的问题是为什么它慢了4倍。这似乎是一个相当大的数目

我使用
%timeit

import numpy as np
b = np.eye(1000)
a = b.tolist()

%timeit b[100][100] #1000000 loops, best of 3: 692 ns per loop
%timeit a[100][100] #10000000 loops, best of 3: 70.7 ns per loop
%timeit b[100,100] #1000000 loops, best of 3: 343 ns per loop
%timeit b.item(100,100) #1000000 loops, best of 3: 297 ns per loop
我试图使用
dis.dis
查看引擎盖下发生了什么,但得到了:

TypeError: don't know how to disassemble method-wrapper objects

然后我试图查看Numpy源代码,但无法找出哪个文件对应于数组元素访问。我很好奇是什么导致了额外的开销,更重要的是,我自己将来如何解决这个问题。看起来python不能很容易地编译成C代码,所以我可以看到不同之处。但是,有没有一种方法可以查看为每行生成的字节码,以了解差异?

当numpy从数组中的一个位置返回项时,它必须将内部C类型(float、double等)值转换为Python类型的标量值(int、long、float)。然后返回对Python类型值的引用。这种转换需要一些时间


有趣的是,同样的低效率在另一方面也会损害绩效。我有一个Python列表,我正在使用来自numpy数组的索引值对其进行索引。在创建Python整型值时也会发生同样的转换,该整型值是索引到Python列表中所需的。我不得不用一个本地Python整数的中间数组重写我的算法。

总之:从NumPy数组中获取项需要创建新的Python对象,而列表则不是这样。此外,NumPy数组的索引比列表稍微复杂一些,这可能会增加一些额外的开销


总而言之,您列出的NumPy操作执行以下操作:

  • b[100][100]
    b
    的第100行作为数组返回,然后获取该行索引100处的值,并将该值作为对象返回(例如a
    np.int64
    type)
  • b[100100]
    直接返回第100行和第100列的值(不首先返回中间数组)
  • b.item(100100)
    执行与上述
    b[100100]
    相同的操作,只是将值转换为本机Python类型并返回
  • 现在,这些操作中,(1)最慢,因为它需要两个连续的NumPy索引操作(我将解释为什么这比下面的列表索引慢)。(2) 是最快的,因为只执行一个索引操作。操作(3)可能较慢,因为它是一个方法调用(在Python中通常较慢)

    为什么列表访问仍然比
    b[100100]

    对象创建 Python列表是指向内存中对象的指针数组。例如,列表
    [1,2,3]
    不直接包含那些整数,而是指向内存地址的指针,因为每个整数对象都已经存在。为了从列表中获取项目,Python只返回对该对象的引用

    NumPy数组不是对象的集合。数组
    np.array([1,2,3])
    只是一个连续的内存块,其位被设置为表示那些整数值。要从该数组中获取整数,必须在与该数组分离的内存中构造一个新的Python对象。例如,索引操作可能返回
    np.int64
    的对象:该对象以前不存在,必须创建

    索引复杂性
    a[100][100]
    (从列表中获取)比
    b[100100]
    (从数组中获取)更快的另外两个原因是:

    • 当索引列表和数组时,字节码操作码
      BINARY_SUBSCR
      会被执行,但是对于Python列表,字节码操作码是经过优化的

    • 处理Python列表整数索引的内部C函数非常简短。另一方面,NumPy索引要复杂得多,需要执行大量代码来确定所使用的索引类型,以便返回正确的值

    下面更详细地描述了使用
    a[100][100]
    b[100100]
    访问列表和数组中元素的步骤

    字节码 列表和数组都会触发相同的四个字节码操作码:

      0 LOAD_NAME                0 (a)           # the list or array
      3 LOAD_CONST               0 (100)         # index number (tuple for b[100,100])
      6 BINARY_SUBSCR                            # find correct "getitem" function
      7 RETURN_VALUE                             # value returned from list or array
    
    注意:如果开始多维列表的链索引,例如
    a[100][100][100]
    ,则开始重复这些字节码指令。使用元组索引的NumPy数组不会出现这种情况:
    b[100100100]
    仅使用四条指令。这就是为什么随着维度数量的增加,计时中的差距开始缩小的原因

    查找正确的“getitem”函数 访问列表和数组的函数是不同的,在每种情况下都需要找到正确的函数。此任务由操作码处理:

    w=POP();//我们的索引
    v=TOP();//我们的列表或NumPy数组
    如果(PyList_CheckExact(v)和&PyInt_CheckExact(w)){//我们有列表和int吗?
    /*内联:列表[int]*/
    Py_ssize_t i=PyInt_AsSsize_t(w);
    if(i<0)
    i+=PyList\u GET\u尺寸(v);
    如果(i>=0&&i
    此代码针对Python列表进行了优化。我