【问题标题】:Set vs. frozenset performance设置与frozenset 性能
【发布时间】:2016-08-01 23:59:59
【问题描述】:

我正在修改 Python 的 setfrozenset 集合类型。

最初,我认为frozenset 会提供比set 更好的查找性能,因为它是不可变的,因此可以利用存储项目的结构。

但是,关于以下实验,情况似乎并非如此:

import random
import time
import sys

def main(n):
    numbers = []
    for _ in xrange(n):
        numbers.append(random.randint(0, sys.maxint))
    set_ = set(numbers)
    frozenset_ = frozenset(set_)

    start = time.time()
    for number in numbers:
        number in set_
    set_duration = time.time() - start

    start = time.time()
    for number in numbers:
        number in frozenset_
    frozenset_duration = time.time() - start

    print "set      : %.3f" % set_duration
    print "frozenset: %.3f" % frozenset_duration


if __name__ == "__main__":
    n = int(sys.argv[1])
    main(n)

我使用 CPython 和 PyPy 执行了这段代码,结果如下:

> pypy set.py 100000000
set      : 6.156
frozenset: 6.166

> python set.py 100000000
set      : 16.824
frozenset: 17.248

在 CPython 和 PyPy 中,frozenset 的查找性能似乎实际上更慢。有人知道为什么会这样吗?我没有研究实现。

【问题讨论】:

  • “因为它是不可变的,因此可以利用存储项目的结构” - 你到底期望它做什么?它可以访问的任何结构,set 也可以访问。
  • 嗯,这就是我要问的。我想也许 freezeset 可以使用某种预先计算的哈希函数,这反过来可以产生更好的查找性能。
  • 您需要计算您查找的任何项目的哈希值,句号。您不能在此处预先计算哈希值,因为您可以针对该集合测试任意项目。我不确定您如何看待这种优化? in 集合中的项目不需要计算其哈希值;它们已经被放入哈希表中。
  • "你需要计算你查找的任何项目的哈希,期间" 我知道这个事实,但仍然有一组固定的元素可以提供优化机会(例如,一个完美的哈希函数可以在生成frozenset的时候生成,可以用于查找)
  • 我同意 Sven 的观点,即通过在创建时进行更多计算,从理论上讲,frozenset 可以在查找时表现更好。例如,对于哈希表实现,可以选择一个哈希函数,以便集合元素的哈希之间的冲突最小。

标签: python performance set frozenset


【解决方案1】:

frozensetset 实现在很大程度上是共享的; set 只是添加了变异方法的frozenset,具有完全相同的哈希表实现。见Objects/setobject.c source file;顶级PyFrozenSet_Type definitionPySet_Type definition 共享功能。

这里没有对 freezeset 进行优化,因为当您测试成员资格时,不需要计算 frozenset 中的项目 in 的哈希值。您用来针对集合测试的项目仍然需要计算其哈希值,以便在集合哈希表中找到正确的槽,以便您可以进行相等性测试。

因此,您的计时结果可能由于系统上正在运行的其他进程而关闭;你测量了挂钟时间,没有禁用 Python 垃圾收集,也没有重复测试相同的东西。

尝试使用timeit module 运行您的测试,其中一个值来自numbers,一个不在集合中:

import random
import sys
import timeit

numbers = [random.randrange(sys.maxsize) for _ in range(10000)]
set_ = set(numbers)
fset = frozenset(numbers)
present = random.choice(numbers)
notpresent = -1
test = 'present in s; notpresent in s'

settime = timeit.timeit(
    test,
    'from __main__ import set_ as s, present, notpresent')
fsettime = timeit.timeit(
    test,
    'from __main__ import fset as s, present, notpresent')

print('set      : {:.3f} seconds'.format(settime))
print('frozenset: {:.3f} seconds'.format(fsettime))

每个测试重复 100 万次并产生:

set      : 0.050 seconds
frozenset: 0.050 seconds

【讨论】:

    【解决方案2】:

    两种不同数据类型的原因不是为了性能,而是为了功能。因为frozensets 是不可变的,所以它们可以用作字典中的键。集合不能用于此目的。

    【讨论】:

    • 小修正:fronzensets 可以用作字典中的键不是因为它们是不可变的,而是因为它们是可散列的(为它们定义了.__hash__() 方法)。
    猜你喜欢
    • 2015-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-28
    相关资源
    最近更新 更多