
总而言之:从NumPy数组中获取项目需要创建新的Python对象,而列表并非如此。同样,对于NumPy数组,索引比列表要稍微复杂一些,这可能会增加一些额外的开销。
回顾一下,您列出的NumPy *** 作执行以下 *** 作:
b[100][100]
返回b
数组的第100行,然后获取该行的索引100处的值,并将该值作为对象(例如np.int64
类型)返回。b[100,100]
直接 在第100行和第100列返回值(首先不返回中间数组)。b.item(100,100)``b[100,100]
除了将值转换为原生Python类型并返回外,其他 *** 作与上述 *** 作相同。
现在,这些 *** 作中,(1)最慢,因为它需要两个连续的NumPy索引 *** 作(我将在下面解释为什么它比列表索引慢)。(2)最快,因为仅执行了一个索引 *** 作。 *** 作(3)可能较慢,因为它是方法调用(在Python中通常较慢)。
为什么 列表 访问仍然比更快
b[100,100]?对象创建
Python列表是指向内存中对象的指针的数组。例如,列表
[1, 2,3]不直接包含那些整数,而是在每个整数对象已经存在的情况下指向内存地址的指针。为了从列表中获得一项,Python只是返回对该对象的引用。
NumPy数组不是对象的集合。该数组
np.array([1, 2,3])只是一个连续的内存块,其位设置为代表这些整数值。要从该数组获取整数,必须在与该数组分开的内存中构造一个新的Python对象。例如,
np.int64索引 *** 作可能会返回一个对象:该对象先前不存在,必须创建。索引复杂度
a[100][100](从列表中获取)比
b[100,100](从数组中获取)更快的另外两个原因是:
BINARY_SUBSCR
索引列表和数组时都执行字节码 *** 作码,但是针对Python列表进行了优化。处理Python列表的整数索引的内部C函数非常简短。另一方面,NumPy索引要复杂得多,需要执行大量代码才能确定所使用的索引类型,以便可以返回正确的值。
下面将详细介绍使用
a[100][100]和访问列表和数组中元素的步骤
b[100,100]。字节码
列表和数组都触发了相同的四个字节码 *** 作码:
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[100,100,100]仅使用四个指令。这就是为什么随着尺寸数量的增加,时序上的差距开始缩小的原因。找到正确的“ getitem”功能
访问列表和数组的功能是不同的,在每种情况下都需要找到正确的函数。此任务由
BINARY_SUBSCR*** 作码处理:
w = POP();// our indexv = TOP();// our list or NumPy arrayif (PyList_CheckExact(v) && PyInt_CheckExact(w)) { // do we have a list and an int? Py_ssize_t i = PyInt_AsSsize_t(w); if (i < 0) i += PyList_GET_SIZE(v); if (i >= 0 && i < PyList_GET_SIZE(v)) { x = PyList_GET_ITEM(v, i); // call "getitem" for lists Py_INCREF(x); } else goto slow_get; } else slow_get: x = PyObject_GetItem(v, w); // else, call another function // to work out what is needed Py_DECREF(v); Py_DECREF(w); SET_TOP(x); if (x != NULL) continue; break;此代码针对Python列表进行了优化。如果该函数看到列表,它将快速调用该函数
PyList_GET_ITEM。现在可以在所需的索引处访问此列表(请参阅下面的下一部分)。
但是,如果没有看到列表(例如,我们有一个NumPy数组),它将采用“
slow_get”路径。这又调用另一个函数
PyObject_GetItem来检查对象映射到哪个“
getitem”函数:
PyObject_GetItem(PyObject *o, PyObject *key){ PyMappingMethods *m; if (o == NULL || key == NULL) return null_error(); m = o->ob_type->tp_as_mapping; if (m && m->mp_subscript) return m->mp_subscript(o, key); ...在NumPy的阵列的情况下,正确的功能位于
mp_subscript在
PyMappingMethods结构。
请注意,在可以调用此正确的“ get”函数之前,还要进行其他函数调用。这些调用会增加的开销
b[100],尽管开销将取决于Python /
NumPy的编译方式,系统架构等。从Python清单获取
在上面可以看到该函数
PyList_GET_ITEM已被调用。这是一个简短的函数,基本上看起来像这样*:
PyList_GetItem(PyObject *op, Py_ssize_t i){ if (!PyList_Check(op)) { // check if list PyErr_BadInternalCall(); return NULL; } if (i < 0 || i >= Py_SIZE(op)) { // check i is in range if (indexerr == NULL) { indexerr = PyUnipre_FromString( "list index out of range"); if (indexerr == NULL) return NULL; } PyErr_SetObject(PyExc_IndexError, indexerr); return NULL; } return ((PyListObject *)op) -> ob_item[i];// return reference to object}*
PyList_GET_ITEM实际上是此函数的宏形式,它执行相同的 *** 作(减去错误检查)。
这意味着使项目在
iPython列表的索引处相对简单。在内部,Python检查所包含的项目的类型是否
i为列表,是否在列表的正确范围内,然后将对引用的引用返回给列表中的对象。从NumPy数组获取
相反,NumPy必须做更多的工作才能返回所请求索引的值。
可以用各种不同的方式对数组建立索引,而NumPy必须决定需要哪个索引例程。各种索引例程主要由中的代码处理
mapping.c。
用于索引NumPy数组的所有内容都将通过该函数
prepare_index,该函数开始解析索引并存储有关广播,维数等的信息。这是该函数的呼叫签名:
NPY_NO_EXPORT intprepare_index(PyArrayObject *self, PyObject *index, npy_index_info *indices, int *num, int *ndim, int *out_fancy_ndim, int allow_boolean)
该功能必须进行很多检查。即使对于诸如的相对简单的索引
b[100,100],也必须推断出很多信息,以便NumPy可以将引用(视图)返回到正确的值。
总之,找到NumPy的“ getitem”函数所花费的时间更长,并且处理数组索引的函数必定比Python列表的单个函数更复杂。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)