我认为公平的比较包括在 Python 3.5 和 3.6 中使用相同的函数和相同的测试条件,以及将 map 与所选 Python 版本中的列表理解进行比较。
在我最初的回答中,我进行了多项测试,结果表明,与列表理解相比,map 在两个 Python 版本中的速度仍然快了大约两倍。但是有些结果还没有定论,所以我进行了更多测试。
首先让我引用您在问题中陈述的一些观点:
"... [我] 注意到 [map] 的相对性能(与类似的列表理解相比)急剧在 Python 3.5 和 3.6 之间发生了变化
你还问:
“我的问题是,在这种情况下发生了什么使列表理解更快而地图解决方案更慢?”
你的意思是 map 比 Python 3.6 中的列表理解慢,还是你的意思是 Python 3.6 中的 map 比 3.5 慢并且列表理解的性能有所提高,目前还不是很清楚(尽管不一定达到跳动的水平map)。
根据我在第一次回答这个问题后进行的更广泛的测试,我想我知道发生了什么。
但是,首先让我们为“公平”比较创造条件。为此,我们需要:
使用相同函数比较map在不同Python版本中的性能;
比较map 的性能与使用相同功能的相同版本中的列表理解;
对相同数据运行测试;
尽量减少计时函数的贡献。
这是关于我的系统的版本信息:
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.
和
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 在两个Python 版本中都不同。这可能导致两个 Python 版本的性能差异。一种解决方法是设置,例如,random.seed(0)(或类似的东西)。我选择创建一次列表并使用numpy.save() 保存它,然后在每个版本中加载它。这一点尤其重要,因为我选择稍微修改您的测试(“循环”和“重复”的数量),并且我已将数据集的长度增加到 100,000,000:
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。这样做的原因来自于 Python 3.5 中执行的以下测试:
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 的结果进行比较:
>>> 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
由于我不知道的原因,与 timeit 包相比,IPython 的魔法 %timeit 增加了一些时间。因此,我将在我的测试中专门使用timeit。
注意:在接下来的讨论中,我将只使用最短时间 (min(t))。
Python 3.5.3 中的测试:
第 1 组:地图和列表理解测试
>>> 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 组:“个人”功能测试
>>> 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 中的测试:
第 1 组:地图和列表理解测试
>>> 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 组:“个人”功能测试
>>> 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 的测试:它在 Python 3.6 中从 Python 3.5 中的 0.0309 秒下降到 0.0268 秒。这使得 Python 3.6 中使用 5 < i 的列表理解测试比 Python 3.5 中的类似测试运行得更快。然而,这并不意味着列表解析在 Python 3.6 中变得更快。
让我们将map 的相对性能与相同Python 版本中相同函数的列表理解进行比较。然后我们进入 Python 3.5:r(f) = 7.4428/4.6666 = 1.595、r(abs) = 5.665/4.167 = 1.359 和 Python 3.6:r(f) = 7.316/4.5997 = 1.591、r(abs) = 6.218/2.743 = 2.267。基于这些相对性能,我们可以看到,在 Python 3.6 中,map 相对于列表理解的性能至少与 Python 3.5 中 f = (5).__lt__ 函数的性能相同,并且对于这样的函数,这个比率甚至有所提高在 Python 3.6 中为 abs()。
无论如何,我相信没有证据表明 Python 3.6 中的列表理解变得更快,无论是相对还是绝对意义上的。唯一的性能改进是 [5 < i for i in lst] 测试,但这是因为 5 < i 本身在 Python 3.6 中变得更快,而不是因为列表理解本身更快。