【问题标题】:Why does `myfloat in myset` become super slow?为什么 `myfloat in myset` 变得超级慢?
【发布时间】:2021-10-31 04:02:14
【问题描述】:

当我将相同的float 值重新插入到我的集合中几次时,应该花费恒定时间的x in s 检查变得非常慢。为什么?

定时输出x in s:

   0.06 microseconds
   0.09 microseconds
   0.16 microseconds
   0.56 microseconds
   1.00 microseconds
   1.58 microseconds
   2.55 microseconds
   5.98 microseconds
  10.50 microseconds
  24.54 microseconds
  40.39 microseconds
  96.60 microseconds
 160.24 microseconds
 419.08 microseconds
 732.27 microseconds

代码(Try it online!):

from timeit import timeit

s = {float('nan')}
for _ in range(15):
    for _ in [*s]:
        x = float('nan')
        s.add(x)
    time = timeit('x in s', number=1000, globals=globals()) * 1e3
    print('%7.2f microseconds' % time)

【问题讨论】:

标签: python performance set


【解决方案1】:

因为您使用的是 nan,它因打破对 __hash__/__eq__ 合同的幼稚期望而臭名昭著……即:

>>> myset = set()
>>> myset.add(float('nan'))
>>> myset
{nan}
>>> myset.add(float('nan'))
>>> myset
{nan, nan}

发生这种情况是因为:

>>> float('nan') == float('nan')
False

但是:

>>> hash(float('nan')) == hash(float('nan'))
True

因此,您可以保证每次都发生碰撞,并且您会看到哈希集行为降级到 O(N),这是最坏情况下的行为,而不是 O(1)。从根本上说,您不会重新插入相同的浮点值

此外,请注意这种行为:

>>> nan = float('nan')
>>> myset = set()
>>> myset.add(nan)
>>> myset.add(nan)
>>> myset
{nan} 

尽管:

>>> nan == nan
False

以上是由于优化,对于容器,Python 实际上首先检查身份以避免潜在的昂贵的__eq__ 操作。由于我重复使用了相同的对象,现在它被认为是“相同的值”。

【讨论】:

  • 没错,你已经把(退化的)哈希集变成了一个链表
  • @Alexander 是的,但要明确的是,我很确定 Python 基于哈希的容器使用开放寻址来解决冲突
  • 啊,那是线性阵列扫描(大致)。更好,但仍然是 O(n),就像你提到的那样
  • 还有一件事:我当然确定你知道它会降级,只是把你的评论当作挑战,因为我之前没有真正看到它降级。然后觉得它很有趣,可以分享,也许对参考有用(didn't take long)。不过,我对使用 nan 锤子并不满意。可能会尝试对 int 散列进行逆向工程,以获得一组具有相等散列的 96 位整数。或者你有另一个想法来创建等哈希集? (当然,除了自定义类。)
  • 查找等哈希整数比我预期的要容易:all(hash(i*(2**61-1)) == 0 for i in range(10**6)).
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-09
  • 1970-01-01
  • 2020-11-22
  • 2021-10-18
  • 1970-01-01
  • 2018-02-24
  • 1970-01-01
相关资源
最近更新 更多