python 展开一个数组的数组,为什么 funtools 比较慢?
python flatten an array of array, why funtools is slower?
基于这个问题
我想要一种比双循环解决方案更快的方法。所以我写了一个基于 functools 的函数,但它似乎慢得多。
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 算法会比double loop 更快吗? 3.any 比双循环快的其他算法?
我觉得至少有两个问题:
对于第一个,您正在创建一个列表并在其中附加元素。但是对于第二个,您将通过 a+b[0].tolist()
连接两个列表,这会产生一个新列表。
functools.reduce
return一个生成器,也就是主要用途。总之,不是为了速度。
这与给 list comprehension vs map 的答案密切相关,因为您将 lambda
与 reduce
语句一起使用,您将发送 python 代码运行 每次迭代,从而减慢减少速度。列表推导旨在更有效和更易读,因此它们是首选方法。
也就是说为什么不使用 itertools.chain.from_iterable
以及 map
ping operator.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]
简而言之,函数调用和列表 re-allocation 开销不同,您使用嵌套循环的算法是 O(N),而使用 reduce 的算法是 O(N²)。
即使算法没有什么不同,调用函数的成本为“0”的想法来自数学,函数是很好的理论结构。
在计算机程序中 运行 时,调用函数需要初始化上下文 - 在 Python 的情况下,创建带有局部变量的 Frame 对象。当你传递参数时,它意味着在一个元组中,参数在函数调用之前构造,并且在函数体中是 de-constructed (尽管这些步骤可能会被实现优化)。
虽然在 2 嵌套循环方法中,您所要做的就是在本机代码中迭代迭代器 - 尽管从理论上讲,根据 Python 的规范,这也意味着调用一个函数(对象的 __iter__
方法),在本机代码迭代器的实际实现中通常要快得多。
然而,这并不能说明您在那里看到的差异。主要问题是对于每次迭代,当执行 a + b[0].tolist()
时,在内存中创建一个新列表 "c","a" 的值被复制到那里,然后 b[0] 的值是附加到它。并且这个新列表+已经扁平化的元素的副本将在每个步骤中发生。在 list-comphrehension 的情况下,没有发生冗余复制 - 当新元素从父 2D 结构展开时放置,并且 Python 被很好地优化为 pre-alocate space 对于在构建时以这种形式增长的列表。
基于这个问题
我想要一种比双循环解决方案更快的方法。所以我写了一个基于 functools 的函数,但它似乎慢得多。
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 算法会比double loop 更快吗? 3.any 比双循环快的其他算法?
我觉得至少有两个问题:
对于第一个,您正在创建一个列表并在其中附加元素。但是对于第二个,您将通过
a+b[0].tolist()
连接两个列表,这会产生一个新列表。functools.reduce
return一个生成器,也就是主要用途。总之,不是为了速度。
这与给 list comprehension vs map 的答案密切相关,因为您将 lambda
与 reduce
语句一起使用,您将发送 python 代码运行 每次迭代,从而减慢减少速度。列表推导旨在更有效和更易读,因此它们是首选方法。
也就是说为什么不使用 itertools.chain.from_iterable
以及 map
ping operator.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]
简而言之,函数调用和列表 re-allocation 开销不同,您使用嵌套循环的算法是 O(N),而使用 reduce 的算法是 O(N²)。
即使算法没有什么不同,调用函数的成本为“0”的想法来自数学,函数是很好的理论结构。
在计算机程序中 运行 时,调用函数需要初始化上下文 - 在 Python 的情况下,创建带有局部变量的 Frame 对象。当你传递参数时,它意味着在一个元组中,参数在函数调用之前构造,并且在函数体中是 de-constructed (尽管这些步骤可能会被实现优化)。
虽然在 2 嵌套循环方法中,您所要做的就是在本机代码中迭代迭代器 - 尽管从理论上讲,根据 Python 的规范,这也意味着调用一个函数(对象的 __iter__
方法),在本机代码迭代器的实际实现中通常要快得多。
然而,这并不能说明您在那里看到的差异。主要问题是对于每次迭代,当执行 a + b[0].tolist()
时,在内存中创建一个新列表 "c","a" 的值被复制到那里,然后 b[0] 的值是附加到它。并且这个新列表+已经扁平化的元素的副本将在每个步骤中发生。在 list-comphrehension 的情况下,没有发生冗余复制 - 当新元素从父 2D 结构展开时放置,并且 Python 被很好地优化为 pre-alocate space 对于在构建时以这种形式增长的列表。