为了提高内存效率,我一直在努力将我的一些代码从列表转换为生成器/迭代器。我发现了许多案例,我只是将我使用代码模式np.array
转换为np.array(some_list)
的列表。
值得注意的是,some_list
通常是一个迭代生成器的列表解析。
我正在调查np.fromiter
以查看我是否可以更直接地使用生成器(而不是必须先将其转换为列表然后将其转换为numpy数组),但我注意到{{1} 1}}函数,与使用现有数据的任何其他数组创建例程不同,需要指定np.fromiter
。
在我的大多数特定情况下,我可以完成这项工作(主要处理loglikelihoods,所以float64会很好),但它让我想知道为什么这只是dtype
数组创建者和不是其他数组创建者。
我的理解是,如果您知道fromiter
和dtype
,则可以将内存分配给生成的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)
会获得什么?
答案 0 :(得分:3)
如果这段代码是十年前编写的,并且没有改变它的压力,那么旧的原因仍然适用。大多数人都很高兴使用np.array
。 np.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.fromiter
和range(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
在生成/迭代期间,对整个数组应用int
到float
转换比对每个元素更快
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)
定义于:
它处理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
。我确信它要复杂得多。