【问题标题】:Efficient creation of numpy arrays from list comprehension and in general从列表理解和一般情况下有效地创建 numpy 数组
【发布时间】:2012-12-31 14:38:54
【问题描述】:

在我目前的工作中,我经常使用 Numpy 和列表​​推导式,为了获得最佳性能,我有以下问题:

如果我按如下方式创建 Numpy 数组,幕后实际会发生什么?

a = numpy.array( [1,2,3,4] )

我的猜测是python首先创建一个包含值的普通列表,然后使用列表大小分配一个numpy数组,然后将值复制到这个新数组中。这是正确的,还是解释器足够聪明,可以意识到列表只是中间值,而是直接复制值?

同样,如果我希望使用 numpy.fromiter() 从列表理解创建一个 numpy 数组:

a = numpy.fromiter( [ x for x in xrange(0,4) ], int )

这会导致在输入fromiter()之前创建一个中间值列表吗?

【问题讨论】:

  • 如果您想避免创建列表,为什么要a = numpy.fromiter( [ x for x in xrange(0,4) ], int ) 而不是简单的a = numpy.fromiter(xrange(4), int)
  • @wim 或只是np.arange ...
  • 只是一个例子(一个可怜的例子,我承认)。表达式可以是任何东西
  • 请注意,如果您需要 np.arange,但我想您可能已经知道了。
  • @wim 提出的观点是,numpy.fromiter(list(something), ...numpy.fromiter([something], ... 应该永远被使用!无论something 是什么,始终使用numpy.fromiter(something, ...

标签: python performance numpy


【解决方案1】:

我相信您正在寻找的答案是将generator expressionsnumpy.fromiter 结合使用。

numpy.fromiter((<some_func>(x) for x in <something>),<dtype>,<size of something>)

生成器表达式是惰性的 - 当您遍历它们时,它们会计算表达式。

使用列表推导生成列表,然后将其输入 numpy,而生成器表达式将一次生成一个。

Python 像大多数语言(如果不是全部)一样,从内到外评估事物,因此使用 [&lt;something&gt; for &lt;something_else&gt; in &lt;something_different&gt;] 会创建列表,然后对其进行迭代。

【讨论】:

  • @JonClements 你可以对x 应用一些函数,它会根据需要进行评估
  • numpy 需要知道生成器的大小才能为其分配内存。 np.fromiter 是如何处理这个问题的?存储生成的值,从而破坏不生成列表或元组的目的?还是运行生成器两次,一次用于计数,另一次用于填充数组?
  • @Jaime 根据文档,如果您将大小指定为count,那么 numpy 将预先分配内存 - 因此,如果您已经将它挂在身边,那么您可以这样做。否则,您是正确的 - 它必须运行生成器,并计算它所生成的列表。
  • @Jaime 生成器只需要运行一次! (想想副作用等等等等。)我没有读过numpy中的fromiter的源代码,但可以肯定numpy.fromiter(something, int)numpy.fromiter(list(something), int)更有效率。 numpy 可以使用malloc/realloc 来创建sizeof(int) 的对象数组。在 Cpython 中,list 是异构对象的可变集合,因此它具有更复杂的数据结构和分配策略。
  • 从文档中很清楚。 指定计数以提高性能。它允许 fromiter 预先分配输出数组,而不是按需调整其大小。 当您达到容量时,它会重新分配数组。与 C++ 中的 std::vector 类似的行为
【解决方案2】:

您可以创建自己的列表并进行试验以了解情况...

>>> class my_list(list):
...     def __init__(self, arg):
...         print 'spam'
...         super(my_list, self).__init__(arg)
...   def __len__(self):
...       print 'eggs'
...       return super(my_list, self).__len__()
... 
>>> x = my_list([0,1,2,3])
spam
>>> len(x)
eggs
4
>>> import numpy as np
>>> np.array(x)
eggs
eggs
eggs
eggs
array([0, 1, 2, 3])
>>> np.fromiter(x, int)
array([0, 1, 2, 3])
>>> np.array(my_list([0,1,2,3]))
spam
eggs
eggs
eggs
eggs
array([0, 1, 2, 3])

【讨论】:

    【解决方案3】:

    对于标题中的问题,现在有一个名为 numba 的包,它支持numpy array comprehension,它直接构造 numpy 数组,无需中间 python 列表。与numpy.fromiter 不同,它还支持嵌套理解。但是,请记住,如果您不熟悉 numba,它会存在一些限制和性能问题。

    也就是说,它可以非常快速和高效,但是如果你可以使用 numpy 的向量操作来编写它,那么让它更简单可能会更好。

    >>> from timeit import timeit
    >>> # using list comprehension
    >>> timeit("np.array([i*i for i in range(1000)])", "import numpy as np", number=1000)
    2.544344299999999
    >>> # using numpy operations
    >>> timeit("np.arange(1000) ** 2", "import numpy as np", number=1000)
    0.05207519999999022
    >>> # using numpy.fromiter
    >>> timeit("np.fromiter((i*i for i in range(1000)), dtype=int, count=1000)",
    ...        "import numpy as np",
    ...        number=1000)
    1.087984500000175
    >>> # using numba array comprehension
    >>> timeit("squares(1000)",
    ... """
    ... import numpy as np
    ... import numba as nb
    ... 
    ... @nb.njit
    ... def squares(n):
    ...     return np.array([i*i for i in range(n)])
    ... 
    ... 'compile the function'
    ... squares(10)
    ... """,
    ... number=1000)
    0.03716940000003888
    

    【讨论】:

      猜你喜欢
      • 2015-06-19
      • 1970-01-01
      • 2016-07-14
      • 2018-02-21
      • 1970-01-01
      • 2019-07-09
      • 1970-01-01
      • 2015-07-13
      • 1970-01-01
      相关资源
      最近更新 更多