首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python3.5与3.6相比,"map“比理解慢

Python3.5与3.6相比,"map“比理解慢
EN

Stack Overflow用户
提问于 2017-08-09 22:37:29
回答 2查看 1.1K关注 0票数 10

如果有一个用C编写的函数/方法,我有时会使用map来获得一些额外的性能。然而,最近我重新讨论了一些基准测试,并注意到相对性能(与类似的列表理解相比)在Python3.5和3.6之间发生了巨大的变化。

这不是实际的代码,只是一个很小的示例,说明了两者的区别:

代码语言:javascript
复制
import random

lst = [random.randint(0, 10) for _ in range(100000)]
assert list(map((5).__lt__, lst)) == [5 < i for i in lst]
%timeit list(map((5).__lt__, lst))
%timeit [5 < i for i in lst]

我意识到使用(5).__lt__不是一个好主意,但我现在无法想出一个有用的例子。

Python-3.5上的时间表支持map方法:

代码语言:javascript
复制
15.1 ms ± 5.64 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
16.7 ms ± 35.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

虽然Python-3.6的时间实际上显示出理解速度更快:

代码语言:javascript
复制
17.9 ms ± 755 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
14.3 ms ± 128 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

我的问题是,在这种情况下发生了什么,使得列表理解速度更快,map解决方案更慢?我意识到差别并不大,这让我很好奇,因为这是我有时(实际上很少)在性能关键代码中使用的“技巧”之一。

EN

回答 2

Stack Overflow用户

发布于 2017-08-10 09:52:40

我认为公平的比较涉及在Python3.5和3.6中使用相同的函数和相同的测试条件,以及在比较map以列出所选版本中的理解时。

在我最初的回答中,我执行了多个测试,这些测试表明,与列表理解相比,在这两个版本的Python中,map的速度仍然提高了大约两倍。然而,有些结果并没有定论,所以我做了更多的测试。

首先,让我引述你在问题中所指出的几点:

“.我注意到[ map__]的相对性能(与类似的列表理解相比)在Python3.5和3.6之间发生了巨大的变化。”

你还会问:

“我的问题是,在这种情况下,是什么使列表理解速度更快,地图解决方案更慢?”

您的意思是地图比Python3.6中的列表理解慢,或者您的意思是Python3.6中的map比3.5慢,并且列表理解的性能已经提高(尽管不一定超过map的水平),这还不太清楚。

根据我对这个问题的第一个答案之后所做的更广泛的测试,我想我已经知道了发生了什么。

然而,首先让我们为“公平”比较创造条件。为此,我们需要:

  1. 使用相同的函数比较不同版本的map的性能;
  2. 使用相同的功能比较map在同一版本中的理解能力;
  3. 对相同的数据进行测试;
  4. 最大限度地减少时间函数的贡献。

以下是有关我的系统的版本信息:

代码语言:javascript
复制
Python 3.5.3 |Continuum Analytics, Inc.| (default, Mar  6 2017, 12:15:08) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
IPython 5.3.0 -- An enhanced Interactive Python.

代码语言:javascript
复制
Python 3.6.2 |Continuum Analytics, Inc.| (default, Jul 20 2017, 13:14:59) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.

让我们首先讨论“相同数据”的问题。不幸的是,由于有效地使用了seed(None),每个数据集lst在每个版本的lst上都是不同的。这可能是两个Python版本的性能差异的原因之一。一个解决办法是设置,例如,random.seed(0) (或类似的东西)。我选择创建一次列表,并使用numpy.save()保存它,然后将其加载到每个版本中。这一点特别重要,因为我选择稍微修改测试(“循环”和“重复”的数量),并将数据集的长度增加到100,000,000:

代码语言:javascript
复制
import numpy as np
import random
lst = [random.randint(0, 10) for _ in range(100000000)]
np.save('lst', lst, allow_pickle=False)

第二,让我们使用timeit模块,而不是IPython的神奇命令%timeit。这样做的原因来自于在Python3.5中执行的以下测试:

代码语言:javascript
复制
In [11]: f = (5).__lt__
In [12]: %timeit -n1 -r20 [f(i) for i in lst]
1 loop, best of 20: 9.01 s per loop

将其与相同版本的Python中的timeit结果进行比较:

代码语言:javascript
复制
>>> t = timeit.repeat('[f(i) for i in lst]', setup="f = (5).__lt__;
... import numpy; lst = numpy.load('lst.npy').tolist()", repeat=20, 
... number=1); print(min(t), max(t), np.mean(t), np.std(t))
7.442819457995938 7.703615028003696 7.5105415405 0.0550515642854

出于未知的原因,IPython的神奇%timeittimeit包增加了一些时间。因此,我将在测试中专门使用timeit

注意:在接下来的讨论中,我将只使用最小定时(min(t))。

Python 3.5.3中的测试:

第一组:地图和列表理解测试

代码语言:javascript
复制
>>> import numpy as np
>>> import timeit

>>> t = timeit.repeat('list(map(f, lst))', setup="f = (5).__lt__; import numpy; lst = numpy.load('lst.npy').tolist()", repeat=20, number=1); print(min(t), max(t), np.mean(t), np.std(t))
4.666553302988177 4.811194089008495 4.72791638025 0.041115884397

>>> t = timeit.repeat('[f(i) for i in lst]', setup="f = (5).__lt__; import numpy; lst = numpy.load('lst.npy').tolist()", repeat=20, number=1); print(min(t), max(t), np.mean(t), np.std(t))
7.442819457995938 7.703615028003696 7.5105415405 0.0550515642854

>>> t = timeit.repeat('[5 < i for i in lst]', setup="import numpy; lst = numpy.load('lst.npy').tolist()", repeat=20, number=1); print(min(t), max(t), np.mean(t), np.std(t))
4.94656751700677 5.07807950800634 5.00670203845 0.0340474956945

>>> t = timeit.repeat('list(map(abs, lst))', setup="import numpy; lst = numpy.load('lst.npy').tolist()", repeat=20, number=1); print(min(t), max(t), np.mean(t), np.std(t))
4.167273573024431 4.320013975986512 4.2408865186 0.0378852782878

>>> t = timeit.repeat('[abs(i) for i in lst]', setup="import numpy; lst = numpy.load('lst.npy').tolist()", repeat=20, number=1); print(min(t), max(t), np.mean(t), np.std(t))
5.664627838006709 5.837686392012984 5.71560354655 0.0456700607748

注意第二个测试(使用f(i)的列表理解)比第三个测试(使用5 < i的列表理解)慢得多,这表明从代码的角度来看,f = (5).__lt__5 < i不完全相同(或几乎相同)。

第2组:“个人”功能测试

代码语言:javascript
复制
>>> t = timeit.repeat('f(1)', setup="f = (5).__lt__", repeat=20, number=1000000); print(min(t), max(t), np.mean(t), np.std(t))
0.052280781004810706 0.05500587198184803 0.0531139718529 0.000877649561967

>>> t = timeit.repeat('5 < 1', repeat=20, number=1000000); print(min(t), max(t), np.mean(t), np.std(t))
0.030931947025237605 0.033691533986711875 0.0314959864045 0.000633274658428

>>> t = timeit.repeat('abs(1)', repeat=20, number=1000000); print(min(t), max(t), np.mean(t), np.std(t))
0.04685414198320359 0.05405496899038553 0.0483296330043 0.00162837880358

请注意,从代码的角度来看,第一个测试( f(1))比第二个测试( 5 < 1)慢得多,进一步支持f = (5).__lt__5 < i不完全相同(或几乎相同)。

Python 3.6.2中的测试:

第一组:地图和列表理解测试

代码语言:javascript
复制
>>> import numpy as np
>>> import timeit

>>> t = timeit.repeat('list(map(f, lst))', setup="f = (5).__lt__; import numpy; lst = numpy.load('lst.npy').tolist()", repeat=20, number=1); print(min(t), max(t), np.mean(t), np.std(t))
4.599696700985078 4.743880658003036 4.6631793691 0.0425774678203

>>> t = timeit.repeat('[f(i) for i in lst]', setup="f = (5).__lt__; import numpy; lst = numpy.load('lst.npy').tolist()", repeat=20, number=1); print(min(t), max(t), np.mean(t), np.std(t))
7.316072431014618 7.572676292009419 7.3837024617 0.0574811241553

>>> t = timeit.repeat('[5 < i for i in lst]', setup="import numpy; lst = numpy.load('lst.npy').tolist()", repeat=20, number=1); print(min(t), max(t), np.mean(t), np.std(t))
4.570452399988426 4.679144663008628 4.61264215875 0.0265541828693

>>> t = timeit.repeat('list(map(abs, lst))', setup="import numpy; lst = numpy.load('lst.npy').tolist()", repeat=20, number=1); print(min(t), max(t), np.mean(t), np.std(t))
2.742673939006636 2.8282236389932223 2.78504617405 0.0260357089928

>>> t = timeit.repeat('[abs(i) for i in lst]', setup="import numpy; lst = numpy.load('lst.npy').tolist()", repeat=20, number=1); print(min(t), max(t), np.mean(t), np.std(t))
6.2177103200228885 6.428813881997485 6.28722427145 0.0493010620999

第2组:“个人”功能测试

代码语言:javascript
复制
>>> t = timeit.repeat('f(1)', setup="f = (5).__lt__", repeat=20, number=1000000); print(min(t), max(t), np.mean(t), np.std(t))
0.051936342992121354 0.05764096099301241 0.0532974587506 0.00117079475737

>>> t = timeit.repeat('5 < 1', repeat=20, number=1000000); print(min(t), max(t), np.mean(t), np.std(t))
0.02675032999832183 0.032919151999522 0.0285137565021 0.00156522182488

>>> t = timeit.repeat('abs(1)', repeat=20, number=1000000); print(min(t), max(t), np.mean(t), np.std(t))
0.047831349016632885 0.0531779529992491 0.0482893927969 0.00112825297875

请注意,从代码的角度来看,第一个测试( f(1))比第二个测试( 5 < 1)慢得多,进一步支持f = (5).__lt__5 < i不完全相同(或几乎相同)。

讨论

我不知道这些时间测试有多可靠,也很难区分所有导致这些计时结果的因素。但是,我们可以从测试的“组2”中注意到,唯一一个显著改变测试时间的“个人”测试是对5 < 1的测试:Python3.6中的测试从Python3.5中的0.0309 s下降到0.0268 s。这使得Python3.6中的列表理解测试比Python3.5中的类似测试运行得更快。然而,这并不意味着在Python3.6中列表理解变得更快。

让我们比较一下map的相对性能,以列出相同版本中相同函数的理解。然后我们学习Python3.5:r(f) = 7.4428/4.6666 = 1.595r(abs) = 5.665/4.167 = 1.359和Python3.6:r(f) = 7.316/4.5997 = 1.591r(abs) = 6.218/2.743 = 2.267。基于这些相对性能,我们可以看到,在Python3.6中,相对于清单理解的性能,map的性能至少与Python3.5中的f = (5).__lt__函数的性能相同,对于Python3.6中的abs()这样的函数,这一比率甚至有所提高。

无论如何,我认为没有证据表明Python3.6中的列表理解在相对意义上和绝对意义上都变得更快了。唯一的性能改进是[5 < i for i in lst]测试,但这是因为5 < i本身在Python3.6中变得更快,而不是因为列表理解本身更快。

票数 5
EN

Stack Overflow用户

发布于 2017-08-09 23:16:07

我认为一个公平的比较将涉及使用相同的功能。在您的例子中,当比较公平时,map仍然获胜:

代码语言:javascript
复制
>>> import sys
>>> print(sys.version)
3.6.2 |Continuum Analytics, Inc.| (default, Jul 20 2017, 13:14:59) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)]
>>> import random
>>> lst = [random.randint(0, 10) for _ in range(100000)]
>>> assert list(map((5).__lt__, lst)) == [5 < i for i in lst]
>>> f = (5).__lt__
>>> %timeit list(map(f, lst))
4.63 ms ± 110 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit [f(i) for i in lst]
9.17 ms ± 177 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

在Python3.5中(至少在我的系统中),map比Python3.6快,列表理解也是如此:

代码语言:javascript
复制
>>> print(sys.version)
3.5.3 |Continuum Analytics, Inc.| (default, Mar  6 2017, 12:15:08) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)]
>>> %timeit list(map(f, lst))
100 loops, best of 3: 4.36 ms per loop
>>> %timeit [f(i) for i in lst]
100 loops, best of 3: 8.12 ms per loop

尽管如此,在使用相同的函数时,map比Python3.5和3.6中的列表理解速度快2倍。

编辑(回复@user2357112注释):

我认为,在回答OP的问题时,进行“公平”比较是很重要的:“我的问题是,在这种情况下发生了什么,使得列表理解更快,地图解决方案更慢?”(最后一段)。然而,在第一段中,@MSeifert说:“.我注意到相对性能(与类似的list 相比)在Python3.5和3.6之间发生了巨大的变化”,也就是说,比较的是maplist comprehension。然而,@MSeifert测试的设置如下:

代码语言:javascript
复制
timig_map_35 = Timing(list(map(f, lst)))
timing_list_35 = Timing([g(i) for i in lst])

这种测试很难找出时间差异的原因:是因为列表理解在3.6中变快了,还是map在3.6中变慢了,或者f(i)在3.6中变慢了,或者g(i)在3.6中变快了呢?

因此,我建议引入f = (5).__lt__,并在map和列表理解测试中使用相同的功能。我还修改了@MSeifert测试,增加了列表中的元素数,并在timeit中编辑了“循环”数。

代码语言:javascript
复制
import random
lst = [random.randint(0, 10) for _ in range(1000000)] # 10x more elements
f = (5).__lt__
%timeit -n1 -r1000 list(map(f, lst)) # f = (5).__lt__
%timeit -n1 -r1000 [f(i) for i in lst] # f(i) = (5).__lt__(i)
%timeit -n1 -r1000 [5 < i for i in lst] # g(i) = 5 < i
%timeit -n1 -r1000 [1 for _ in lst] # h(i) = 1

在Python3.6中,我得到:

代码语言:javascript
复制
43.5 ms ± 1.79 ms per loop (mean ± std. dev. of 1000 runs, 1 loop each)
82.2 ms ± 2.39 ms per loop (mean ± std. dev. of 1000 runs, 1 loop each)
43.6 ms ± 1.64 ms per loop (mean ± std. dev. of 1000 runs, 1 loop each)
23.8 ms ± 1.27 ms per loop (mean ± std. dev. of 1000 runs, 1 loop each)

在Python3.5中,我得到:

代码语言:javascript
复制
1 loop, best of 1000: 43.7 ms per loop
1 loop, best of 1000: 78.9 ms per loop
1 loop, best of 1000: 46 ms per loop
1 loop, best of 1000: 26.8 ms per loop

在我看来,这表明列表理解速度在3.6略快于3.5,除非使用f。因此,很难断定在Python3.6中速度较慢的是map,或者上面的第一个timeit是慢的,因为对f的调用比较慢。因此,我又进行了两次测试:

代码语言:javascript
复制
%timeit -n1 -r1000 list(map(abs, lst))
%timeit -n1 -r1000 [abs(i) for i in lst]
%timeit -n1000000 -r1000 f(1)

在Python3.6中,我得到:

代码语言:javascript
复制
25.8 ms ± 1.42 ms per loop (mean ± std. dev. of 1000 runs, 1 loop each)
67.1 ms ± 2.07 ms per loop (mean ± std. dev. of 1000 runs, 1 loop each)
64.7 ns ± 2.22 ns per loop (mean ± std. dev. of 1000 runs, 1000000 loops each)

在Python3.5中,我得到:

代码语言:javascript
复制
1 loop, best of 1000: 38.3 ms per loop
1 loop, best of 1000: 56.4 ms per loop
1000000 loops, best of 1000: 59.6 ns per loop

这表明,对于某些函数,map比列表理解要快得多:具体来说,map的相对性能与Python3.6中的“列表理解”是67.1/25.8 = 2.60,而在Python3.5中是56.4/38.3 = 1.47。因此,有趣的是,为什么@MSeifert测试显示map在Python3.6中速度较慢。我上面的最后一个测试显示了f(1)“单独”的时间测试。我不知道这个测试是否有效(不幸的是)--我想避免使用map[for]来消除一个变量--但它表明,在Python3.6中,f = (5).__lt__比Python3.5慢。因此,我认为它是函数f ((5).__lt__)的特殊形式,它的计算速度减慢,而不是map函数。我知道最后一次“单独”测试可能是一个糟糕的测试,然而,map在与abs一起使用时非常快(相对或绝对)这一事实表明,问题在f中而不是在map中。

注意:Python3.5使用IPython 5.3.0,Python3.6使用IPython 6.1.0。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/45601663

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档