为什么numpy的fromiter函数需要在其他数组创建例程中指定dtype?

时间:2015-12-01 22:04:19

标签: python arrays numpy generator memory-efficient

为了提高内存效率,我一直在努力将我的一些代码从列表转换为生成器/迭代器。我发现了许多案例,我只是将我使用代码模式np.array转换为np.array(some_list)的列表。

值得注意的是,some_list通常是一个迭代生成器的列表解析。

我正在调查np.fromiter以查看我是否可以更直接地使用生成器(而不是必须先将其转换为列表然后将其转换为numpy数组),但我注意到{{1} 1}}函数,与使用现有数据的任何其他数组创建例程不同,需要指定np.fromiter

在我的大多数特定情况下,我可以完成这项工作(主要处理loglikelihoods,所以float64会很好),但它让我想知道为什么这只是dtype数组创建者和不是其他数组创建者。

首先尝试猜测:

内存预分配?

我的理解是,如果您知道fromiterdtype,则可以将内存分配给生成的count,如果您没有指定可选内容np.array论证它将"按需调整输出数组的大小"。但是,如果您没有指定计数,那么您似乎应该能够以正常count调用中的相同方式动态推断dtype

数据类型重铸?

我可以看到这对于将数据重新转换为新的np.array是有用的,但这也适用于其他数组创建例程,并且看起来值得作为可选但不是必需的参数放置。

重述问题的几种方法

那么为什么你需要指定dtype来使用dtype;或者换句话说,如果要按需要调整数组的大小,指定np.fromiter会产生什么收益呢?

与我的问题更直接相关的同一问题的更微妙的版本: 我知道dtype的许多效率提升会在您不断调整大小时失去,那么使用np.ndarray而不是np.fromiter(generator,dtype=d)而不是np.fromiter([gen_elem for gen_elem in generator],dtype=d)会获得什么?

1 个答案:

答案 0 :(得分:3)

如果这段代码是十年前编写的,并且没有改变它的压力,那么旧的原因仍然适用。大多数人都很高兴使用np.arraynp.fromiter主要用于那些试图从迭代生成值的方法中挤出一些速度的人。

我的印象是np.array,主要替代方案在决定dtype(和其他属性)之前读取/处理整个输入:

我可以通过改变一个元素强制浮动返回:

In [395]: np.array([0,1,2,3,4,5])
Out[395]: array([0, 1, 2, 3, 4, 5])
In [396]: np.array([0,1,2,3,4,5,6.])
Out[396]: array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.])

我没有多使用fromiter,但我的感觉是,通过要求dtype,它可以从一开始就将输入转换为该类型。这可能最终产生更快的迭代,但这需要时间测试。

我知道np.array的一般性来自某个时间成本。通常对于小型列表,使用列表推导比将其转换为数组更快 - 即使数组操作很快。

一些时间测试:

In [404]: timeit np.fromiter([0,1,2,3,4,5,6.],dtype=int)
100000 loops, best of 3: 3.35 µs per loop
In [405]: timeit np.fromiter([0,1,2,3,4,5,6.],dtype=float)
100000 loops, best of 3: 3.88 µs per loop
In [406]: timeit np.array([0,1,2,3,4,5,6.])
100000 loops, best of 3: 4.51 µs per loop
In [407]: timeit np.array([0,1,2,3,4,5,6])
100000 loops, best of 3: 3.93 µs per loop

差异很小,但建议我的推理是正确的。要求dtype有助于加快fromiter的速度。 count在这么小的尺寸上没有任何区别。

奇怪的是,为dtype指定np.array可以减慢它的速度。好像它附加了astype电话:

In [416]: timeit np.array([0,1,2,3,4,5,6],dtype=float)
100000 loops, best of 3: 6.52 µs per loop
In [417]: timeit np.array([0,1,2,3,4,5,6]).astype(float)
100000 loops, best of 3: 6.21 µs per loop

当我使用np.array(Python3生成器版本)时,np.fromiterrange(1000)之间的差异更为显着

In [430]: timeit np.array(range(1000))
1000 loops, best of 3: 704 µs per loop

实际上,将范围转换为列表更快:

In [431]: timeit np.array(list(range(1000)))
1000 loops, best of 3: 196 µs per loop

fromiter仍然更快:

In [432]: timeit np.fromiter(range(1000),dtype=int)
10000 loops, best of 3: 87.6 µs per loop

在生成/迭代期间,对整个数组应用intfloat转换比对每个元素更快

In [434]: timeit np.fromiter(range(1000),dtype=int).astype(float)
10000 loops, best of 3: 106 µs per loop
In [435]: timeit np.fromiter(range(1000),dtype=float)
1000 loops, best of 3: 189 µs per loop

请注意,astype调整大小操作并不昂贵,只有大约20μs。

============================

array_fromiter(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *keywds)定义于:

https://github.com/numpy/numpy/blob/eeba2cbfa4c56447e36aad6d97e323ecfbdade56/numpy/core/src/multiarray/multiarraymodule.c

它处理keywds并调用 PyArray_FromIter(PyObject *obj, PyArray_Descr *dtype, npy_intp count)https://github.com/numpy/numpy/blob/97c35365beda55c6dead8c50df785eb857f843f0/numpy/core/src/multiarray/ctors.c

这使用定义的ret

创建一个初始数组dtype
ret = (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type, dtype, 1,
                                            &elcount, NULL,NULL, 0, NULL);

此数组的data属性随50% overallocation => 0, 4, 8, 14, 23, 36, 56, 86 ...一起增长,并缩小到最后。

这个数组的dtype PyArray_DESCR(ret)显然有一个函数可以取value(由迭代器next提供),转换它,并在{{1}中设置它}}

data

换句话说,所有dtype转换都是由定义的dtype完成的。如果它“即时”决定如何转换`(PyArray_DESCR(ret)->f->setitem(value, item, ret)` (以及之前分配的所有代码),代码将会复杂得多。此函数中的大多数代码都处理分配value缓冲区。

我会推迟查找data。我确信它要复杂得多。