【问题标题】:Python: Membership testing way slower with frozenset than sets, tuples and lists?Python:使用 freezeset 的成员资格测试方式比集合、元组和列表慢?
【发布时间】:2019-09-10 20:28:02
【问题描述】:

当我掉进那个兔子洞时,我已经阅读了几个小时试图了解会员测试和速度。我以为我已经得到了它,直到我运行了自己的小 timeit 测试

这是代码

range_ = range(20, -1, -1)
w = timeit.timeit('0 in {seq}'.format(seq=list(range_)))
x = timeit.timeit('0 in {seq}'.format(seq=tuple(range_)))
y = timeit.timeit('0 in {seq}'.format(seq=set(range_)))
z = timeit.timeit('0 in {seq}'.format(seq=frozenset(range_)))
print('list:', w)
print('tuple:', x)
print('set:', y)
print('frozenset:', z)

这是结果

列表:0.3762843

元组:0.38087859999999996

设置:0.06568490000000005

冻结集:1.5114070000000002

具有相同时间的列表和元组是有意义的。 我认为 set 和 freezeset 也会有相同的时间,但即使与列表相比它也非常慢?

将代码更改为以下代码仍然会给我类似的结果:

list_ = list(range(20, -1, -1))
tuple_ = tuple(range(20, -1, -1))
set_ = set(range(20, -1, -1))
frozenset_ = frozenset(range(20, -1, -1))

w = timeit.timeit('0 in {seq}'.format(seq=list_))
x = timeit.timeit('0 in {seq}'.format(seq=tuple_))
y = timeit.timeit('0 in {seq}'.format(seq=set_))
z = timeit.timeit('0 in {seq}'.format(seq=frozenset_))

【问题讨论】:

  • 我猜是因为没有文字形式。如果您在没有创建的情况下进行测试会发生什么?
  • @jonrrsharpe 对不起,没有创作是什么意思?
  • 您正在计时将范围转换为每种对象类型并查找值所需的时间,而不仅仅是计时查找
  • 所以不是每次都创建一个新的frozenset(这也创建了一个集合;同样,没有文字形式),只需在循环中包含成员资格测试。跨度>
  • 您的新代码仍然遇到与原始代码相同的问题,因为对于 freezeset 的情况,'0 in {seq}'.format(seq=frozenset_) 给出了'0 in frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20})',所以正如@jonrsharpe 所说,您正在构建一个集合,然后制作一个frozenset,并且只有then测试成员资格。

标签: python list set timeit frozenset


【解决方案1】:

这不是会员资格测试,而是建设需要时间。

考虑以下几点:

import timeit

list_ = list(range(20, -1, -1))
tuple_ = tuple(range(20, -1, -1))
set_ = set(range(20, -1, -1))
frozenset_ = frozenset(range(20, -1, -1))

w = timeit.timeit('0 in list_', globals=globals())
x = timeit.timeit('0 in tuple_', globals=globals())
y = timeit.timeit('0 in set_', globals=globals())
z = timeit.timeit('0 in frozenset_', globals=globals())

print('list:', w)
print('tuple:', x)
print('set:', y)
print('frozenset:', z)

我在 Python 3.5 中得到以下时间:

list: 0.28041897085495293
tuple: 0.2775509520433843
set: 0.0552431708201766
frozenset: 0.05547476885840297

下面将通过反汇编您要进行基准测试的代码来演示为什么frozenset 的速度如此之慢:

import dis

def print_dis(code):
  print('{code}:'.format(code=code))
  dis.dis(code)

range_ = range(20, -1, -1)
print_dis('0 in {seq}'.format(seq=list(range_)))
print_dis('0 in {seq}'.format(seq=tuple(range_)))
print_dis('0 in {seq}'.format(seq=set(range_)))
print_dis('0 in {seq}'.format(seq=frozenset(range_)))

它的输出是不言自明的:

0 in [20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]:
  1           0 LOAD_CONST               0 (0)
              3 LOAD_CONST              21 ((20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE
0 in (20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0):
  1           0 LOAD_CONST               0 (0)
              3 LOAD_CONST              21 ((20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE
0 in {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}:
  1           0 LOAD_CONST               0 (0)
              3 LOAD_CONST              21 (frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE
0 in frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}):
  1           0 LOAD_CONST               0 (0)
              3 LOAD_NAME                0 (frozenset)
              6 LOAD_CONST               0 (0)
              9 LOAD_CONST               1 (1)
             12 LOAD_CONST               2 (2)
             15 LOAD_CONST               3 (3)
             18 LOAD_CONST               4 (4)
             21 LOAD_CONST               5 (5)
             24 LOAD_CONST               6 (6)
             27 LOAD_CONST               7 (7)
             30 LOAD_CONST               8 (8)
             33 LOAD_CONST               9 (9)
             36 LOAD_CONST              10 (10)
             39 LOAD_CONST              11 (11)
             42 LOAD_CONST              12 (12)
             45 LOAD_CONST              13 (13)
             48 LOAD_CONST              14 (14)
             51 LOAD_CONST              15 (15)
             54 LOAD_CONST              16 (16)
             57 LOAD_CONST              17 (17)
             60 LOAD_CONST              18 (18)
             63 LOAD_CONST              19 (19)
             66 LOAD_CONST              20 (20)
             69 BUILD_SET               21
             72 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             75 COMPARE_OP               6 (in)
             78 RETURN_VALUE

【讨论】:

    【解决方案2】:

    这是因为在您将范围对象转换为的 4 种数据类型中,frozenset 是 Python 3 中唯一需要以文字形式进行名称查找的数据类型,并且名称查找很昂贵,因为它需要对字符串进行哈希处理名称,然后通过本地、全局和内置命名空间查找它:

    >>> repr(list(range(3)))
    '[0, 1, 2]'
    >>> repr(tuple(range(3)))
    '(0, 1, 2)'
    >>> repr(set(range(3)))
    '{0, 1, 2}'
    >>> repr(frozenset(range(3)))
    'frozenset([0, 1, 2])' # requires a name lookup when evaluated by timeit
    

    在 Python 2 中,集合在由 repr 转换时也需要名称查找,这就是为什么 @NPE 在评论中报告说在 Python 2 中 frozensetset 之间的性能差别不大:

    >>> repr(set(range(3)))
    'set([0, 1, 2])'
    

    【讨论】:

    • 它是如何进行名称查找的?通过名称查找,您的意思是,什么,在方法/循环中查找“frozenset”(如果它在一个中)然后是文件然后是内置的东西?
    • @user1021085 到 lookup name frozenset
    • 因为timeit.timeit 调用exec 将给定的字符串作为Python 语句/表达式编译和执行,它必须查找名称frozenset 才能将其解析为函数对象。跨度>
    • 我想这与可供选择的符号有关。毕竟只有这么多的符号,只有{}()[]<>可以合理地用作外壳,但是由于前3个都被占用了,而<>已经被用作比较运算符,1<2>0 正在进行有效的链式比较,它没有给frozenset 文字留下好的选择,可以使用另一对符号作为外壳,而不会使代码看起来难看、混乱或模棱两可。
    • 当你使用一个没有名字的集合文字时,它确实被优化为frozenset,因此它可以直接作为常量加载,但优化是在编译时完成的,所以有在运行时没有惩罚。另一方面,您的 frozenset 在运行时会转换为对 frozenset 的函数调用,因此存在名称查找和函数调用的开销。
    猜你喜欢
    • 1970-01-01
    • 2011-07-10
    • 1970-01-01
    • 2021-10-10
    • 2011-10-29
    • 2011-07-11
    • 1970-01-01
    • 2012-03-02
    相关资源
    最近更新 更多