【问题标题】:why is converting a long 2D list to numpy array so slow?为什么将长 2D 列表转换为 numpy 数组这么慢?
【发布时间】:2013-08-01 03:54:54
【问题描述】:

我有一长串 xy 坐标,想把它转换成 numpy 数组。

>>> import numpy as np
>>> xy = np.random.rand(1000000, 2).tolist()

显而易见的方法是:

>>> a = np.array(xy) # Very slow...

但是,上面的代码慢得不合理。有趣的是,先转置长列表,将其转换为 numpy 数组,然后转回会快得多(在我的笔记本电脑上是 20 倍)。

>>> def longlist2array(longlist):
...     wide = [[row[c] for row in longlist] for c in range(len(longlist[0]))]
...     return np.array(wide).T
>>> a = longlist2array(xy) # 20x faster!

这是numpy的bug吗?

编辑:

这是一个动态生成的点列表(带有 xy 坐标),因此我认为当前的表示是最自然的,而不是预先分配一个数组并在必要时扩大它,或者为 x 和 y 维护两个 1D 列表.

既然我们在双向遍历 python 列表,为什么循环第二个索引比第一个索引快?

编辑 2:

根据@tiago 的回答和this question,我发现以下代码的速度是原始版本的两倍:

>>> from itertools import chain
>>> def longlist2array(longlist):
...     flat = np.fromiter(chain.from_iterable(longlist), np.array(longlist[0][0]).dtype, -1) # Without intermediate list:)
...     return flat.reshape((len(longlist), -1))

【问题讨论】:

  • 这不是错误,而是功能!
  • 那么这个功能有什么用呢?我唯一能想到的就是检查每个内部列表的长度是否相同,但我认为不会花这么长时间......
  • @herrlich10 列表在内存中不一定是连续的,因此np.array 循环遍历第一个索引(列表索引)并将其添加到数组中。这就是为什么当第一个索引比第二个大得多时需要更长的时间。
  • @tiago 遵循类似的逻辑,内部列表在内存中也可能不连续。为什么循环第二个索引这么快?

标签: python performance numpy


【解决方案1】:

在 Cython 中实现此功能,无需额外检查以确定维度等,几乎可以消除您所看到的时间差异。 这是我用来验证的.pyx 文件。

from numpy cimport ndarray as ar
import numpy as np
cimport cython

@cython.boundscheck(False)
@cython.wraparound(False)
def toarr(xy):
    cdef int i, j, h=len(xy), w=len(xy[0])
    cdef ar[double,ndim=2] new = np.empty((h,w))
    for i in xrange(h):
        for j in xrange(w):
            new[i,j] = xy[i][j]
    return new

我假设额外的时间用于检查每个子列表的长度和内容,以确定所需数组的数据类型、维度和大小。 当只有两个子列表时,只需要检查两个长度来确定数组中的列数,而不是检查其中的 1000000 个。

【讨论】:

  • 这很有意义。谢谢你,IanH。
  • 顺便说一句,如果您正在寻找更快的实现,我在此处包含的 Cython 在任何一种情况下都比内置版本快很多,因为它完全绕过了检查。但它并不一般。
  • 如果我们保留 boundscheck(True) 和 wraparound(True),只用 cython 做这两个 for 循环,会不会和直接 np.array(xy) 方法一样慢?跨度>
  • 在这种情况下,我不确定为什么需要将它们设置为 True,优化的索引仅适用于数组,而不适用于列表,因此越界内存访问不是不会发生。话虽如此,我运行了一些快速基准测试并没有太大变化。它们在这里,对于 1000000 2D pts:原始列表:Cython(如上)98.5ms,Cython(无额外指令)103ms,纯 Python 循环 870ms,NumPy 内置 6.41s,转置列表:Cython(如上)85.3ms,Cython (没有额外说明)92.5ms,Python 527ms,NumPy,289ms。我没有包括转换列表所花费的时间。
  • 只是验证这些额外检查是否真的是 Numpy builtin 性能不佳的原因,这仍然很难相信:)
【解决方案2】:

这是因为列表中变化最快的索引是最后一个,因此np.array() 必须多次遍历数组,因为第一个索引要大得多。如果您的列表被转置,np.array() 将比您的 longlist2array 更快:

In [65]: import numpy as np

In [66]: xy = np.random.rand(10000, 2).tolist()

In [67]: %timeit longlist2array(xy)
100 loops, best of 3: 3.38 ms per loop

In [68]: %timeit np.array(xy)
10 loops, best of 3: 55.8 ms per loop

In [69]: xy = np.random.rand(2, 10000).tolist()

In [70]: %timeit longlist2array(xy)
10 loops, best of 3: 59.8 ms per loop

In [71]: %timeit np.array(xy)
1000 loops, best of 3: 1.96 ms per loop

您的问题没有神奇的解决方案。这就是 Python 将列表存储在内存中的方式。你真的需要一个具有这种形状的列表吗?不能逆转吗? (考虑到您正在转换为 numpy,您真的需要一个列表吗?)

如果你必须转换一个列表,这个函数比你的longlist2array快10%左右:

from itertools import chain

def convertlist(longlist)
    tmp = list(chain.from_iterable(longlist))
    return np.array(tmp).reshape((len(longlist), len(longlist[0])))

【讨论】:

  • 与维度顺序肯定相关,但我想知道为什么影响如此之大,因为 numpy 是用 C/C++ 实现的。感谢 itertools 解决方案!
  • @herrlich10:列表是高级对象,因此 numpy 是用 C 编写的这一事实并没有使任何事情变得更快:它仍然必须处理 Python 对象。
【解决方案3】:

如果你有 pandas,可以使用pandas.lib.to_object_array(),这是最快的方法:

import numpy as np
import pandas as pd
a = np.random.rand(100000, 2)
b = a.tolist()

%timeit np.array(b, dtype=float, ndmin=2)
%timeit np.array(b, dtype=object).astype(float)
%timeit np.array(zip(*b)).T
%timeit pd.lib.to_object_array(b).astype(float)

输出:

1 loops, best of 3: 462 ms per loop
1 loops, best of 3: 192 ms per loop
10 loops, best of 3: 39.9 ms per loop
100 loops, best of 3: 13.7 ms per loop

【讨论】:

  • 谢谢。它确实比 flatten 生成器方法快约 30%,尽管需要额外的包。
  • 此解决方案似乎已被弃用,因为此属性在 pandas 中不再存在。 AttributeError: module 'pandas' has no attribute 'lib'。在 github 上也有一个关于这个的线程:github.com/Neurosim-lab/netpyne/issues/406
猜你喜欢
  • 2019-06-11
  • 1970-01-01
  • 1970-01-01
  • 2016-03-25
  • 2012-04-01
  • 2011-05-19
  • 2013-06-30
相关资源
最近更新 更多