Python扁平化数组,为什么Funtools更慢?



基于这个问题,python扁平化数组

我想要一种比双循环解决方案更快的方法。 所以我写了一个基于函数的函数,但它似乎慢得多。

orders2.shape
(9966, 1)
import time
t0 = time.time()
[x for i in orders2.values.tolist() for x in i[0].tolist()]
t1 = time.time()
t1-t0
0.009984493255615234
import time
t0 = time.time()
functools.reduce(lambda a,b: a+b[0].tolist() , orders2.values.tolist(), [])
t1 = time.time()
t1-t0
1.4101920127868652

我的问题是 1. 怎么会这样?2. 使用大数据时,functools算法会比双循环更快吗?3.还有其他比双循环更快的算法吗?

简而言之,函数调用和列出重新分配开销分开,具有嵌套循环的算法是 O(N),使用 reduce 的算法是 O(N²)。

即使算法不会有所不同,调用函数具有"0"成本的想法来自数学,函数是很好的理论结构。

在计算机程序中运行时,调用函数需要初始化上下文 - 对于Python,使用局部变量创建Frame对象。当你有参数被传递时,它意味着在一个元组中,参数在函数调用之前被构造,并在函数体中被解构(尽管这些步骤可能会通过实现进行优化)。

在2 嵌套循环方法中,您所要做的就是在本机代码中迭代迭代器 - 尽管理论上,根据 Python 的规范,这也意味着调用一个函数(对象的__iter__方法),但在本机代码迭代器的实际实现中,它通常要快得多。

然而,这并不能解释你在那里看到的差异。主要问题是,对于每个迭代操作,当执行a + b[0].tolist()在内存中创建新列表"c"时,"a"的值被复制到那里,然后将b[0]中的值附加到其中。这个新列表 + 已经展平的元素的副本将在每一步中进行。在列表理解的情况下,没有多余的副本发生 - 当它们从父 2D 结构的展开中出现时,会放置一个新元素,并且 Python 经过很好的优化,可以预先为构建时增长的列表预先分配空间,以这种形式。

这与列出理解与映射给出的答案密切相关,因为您在reduce语句中使用lambda,您将发送python代码以运行每次迭代,从而减慢了归约速度。列表推导旨在更加高效和可读,因此它们是首选方法。

也就是说,为什么不使用itertools.chain.from_iterable以及mappingoperator.itemgetter。这导致相同的输出,同时还利用了一些很棒的内置方法。尚未测试速度

>>> from itertools import chain
>>> from operator import itemgetter
>>> arr = array([[array([33120, 28985,  9327, 45918, 30035, 17794, 40141,  1819, 43668],
dtype='int64')],
[array([33754, 24838, 17704, 21903, 17668, 46667, 17461, 32665],
dtype='int64')],
[array([46842, 26434, 39758, 27761, 10054, 21351, 22598, 34862, 40285,
17616, 25146, 32645, 41276], dtype='int64')],
[array([24534,  8230, 14267,  9352,  3543, 29397,   900, 32398, 34262,
37646, 11930, 37173], dtype='int64')],
[array([25157], dtype='int64')],
[array([ 8859, 20850, 19322,  8075], dtype='int64')]], dtype=object)
>>> array(list(chain.from_iterable(map(itemgetter(0),arr.tolist()))))
[33120 28985  9327 45918 30035 17794 40141  1819 43668 33754 24838 17704
21903 17668 46667 17461 32665 46842 26434 39758 27761 10054 21351 22598
34862 40285 17616 25146 32645 41276 24534  8230 14267  9352  3543 29397
900 32398 34262 37646 11930 37173 25157  8859 20850 19322  8075]

我认为至少有两个问题:

  1. 使用第一个,您将创建一个列表并在其中附加元素。但是对于第二个,您通过a+b[0].tolist()不断连接两个列表,这会产生一个新列表。

  2. functools.reduce返回生成器,这是主要目的。简而言之,它不是为了速度。

最新更新