gpt4 book ai didi

Python 3.5 与 3.6 相比,是什么让 "map"比理解更慢

转载 作者:太空狗 更新时间:2023-10-29 18:34:50 25 4
gpt4 key购买 nike

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

这不是实际的代码,而只是说明差异的最小示例:

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方法:
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 计时实际上表明理解速度更快:
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解慢?我意识到差异并不大,它只是让我感到好奇,因为这是我有时(实际上很少)在性能关键代码中使用的“技巧”之一。

最佳答案

我认为公平的比较涉及在 Python 3.5 和 3.6 中使用相同的函数和相同的测试条件,以及将 map 与所选 Python 版本中的列表理解进行比较。

在我最初的回答中,我进行了多次测试,结果表明与列表理解相比,map 在两个版本的 Python 中仍然快了大约两倍。然而,有些结果并不是决定性的,所以我进行了更多的测试。

首先让我引用您在问题中陈述的一些观点:

"... [I] noticed that the relative performance [of map] (compared to a similar list comprehension) drastically changed between Python 3.5 and 3.6"



你还问:

"My question is what happened in this case that made the list-comprehension faster and the map solution slower?"



如果您的意思是 map 比 Python 3.6 中的列表推导慢,或者您的意思是 Python 3.6 中的 map 比 3.5 中的 map 慢并且列表推导的性能有所提高(尽管不一定达到 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) ,因此两个 Python 版本中的每个数据集 lst 都不同。这可能导致在两个 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 组: map 和列表理解测试
    >>> 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 组: map 和列表理解测试
    >>> 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.5 中的 0.0309s 下降到 Python 3.6 中的 0.0268s。这使得使用 5 < i 的 Python 3.6 中的列表理解测试比 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 中的 abs() 等函数中有所提高3.6.

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

    关于Python 3.5 与 3.6 相比,是什么让 "map"比理解更慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45601663/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com