&引用;真正的埃拉托什尼筛”;在Python中,为什么heapq比dict慢?

&引用;真正的埃拉托什尼筛”;在Python中,为什么heapq比dict慢?,python,performance,heap,sieve-of-eratosthenes,Python,Performance,Heap,Sieve Of Eratosthenes,接下来,我尝试在Python中实现Eratosthenes筛的一些懒惰、无限版本。我惊讶地发现基于堆的版本,论文声称应该运行得更快,实际上对我来说慢了两倍多 本文包含两个示例,一个基于dict,我已翻译(来自Haskell),因此: 本文的第二个示例演示了如何使用优先级队列作为数据结构。它还使用惰性列表,而不是一个简单的增量,为了公平测试,我没有这样做。(此外,我使用了itertools.count的实例来处理我的懒惰列表,我发现它运行得稍微慢一些) 我对这两个参数进行了计时,同时还有一个“渴望

接下来,我尝试在Python中实现Eratosthenes筛的一些懒惰、无限版本。我惊讶地发现基于堆的版本,论文声称应该运行得更快,实际上对我来说慢了两倍多

本文包含两个示例,一个基于dict,我已翻译(来自Haskell),因此:

本文的第二个示例演示了如何使用优先级队列作为数据结构。它还使用惰性列表,而不是一个简单的增量,为了公平测试,我没有这样做。(此外,我使用了
itertools.count
的实例来处理我的懒惰列表,我发现它运行得稍微慢一些)

我对这两个参数进行了计时,同时还有一个“渴望”的版本,没有在这里复制,它生成了一个低于某个极限的所有素数的列表:

In [44]: from itertools import islice

In [45]: %timeit list(islice(dict_sieve(), 100000))
    ...: %timeit list(islice(heap_sieve(), 100000))
    ...: %timeit eager_sieve(1299710)  # 1299709 is the 100,000th prime
    ...: 
1 loops, best of 3: 2.12 s per loop
1 loops, best of 3: 4.94 s per loop
1 loops, best of 3: 677 ms per loop

“渴望”版本的速度快得多并不奇怪——它基本上是在内存使用、必须指定上限的不便和CPU时间之间进行权衡。然而,我确实感到惊讶的是,
heapq
版本的速度要慢得多,而这篇论文声称它更高效。这是我的实现有问题吗?或者,正如我们所知,dict是超快速的(并且
heapq
相对较慢)?

实际上,基于字典的方法应该比基于堆队列的方法更快。堆插入和替换操作是O(log n),而字典插入和替换操作是O(1)


事实上,我很惊讶地听到论文作者提出了相反的观点。但事实并非如此。您假设
Data.Map
是作为哈希映射实现的,但实际上,它是一个。因此,它的性能特征与堆队列的性能特征非常相似。区别在于从堆队列中检索最小密钥是O(1),这加快了部分筛选代码的速度,但哈希映射速度更快

回答得好!非常感谢。你知道Data.Map为什么不使用哈希表吗?hashtable数据结构不适合函数方法吗?@poorsod,这是我经常问自己的问题,但从来没有给出满意的答案。
Data
中有一个类型,所以这似乎不是不可能的。@poorsod:在二叉树中有很多事情你可以做,但在哈希表中你做不好,比如范围查询,或者获取min/max元素。另一个因素是,在编写
Data.Map
时,我怀疑Haskell中的数组支持是否很好。@senderle感谢您的编辑。我一辈子都把他的名字拼错了,却没有意识到!是的,这是一个相当小的编辑,但似乎值得,因为这是一个容易拼错的名字。我不得不自己仔细检查拼写!
from itertools import count
from heapq import heappush, heapreplace
def heap_sieve():
    yield 2
    yield 3
    candidates = count(5,2)
    composites = [(9, 3)]  # a priority queue of composite/factor pairs, keyed by composite

    for candidate in candidates:
        prime_flag = True
        while composites[0][0] == candidate:  # loop because there may be duplicates
            prime_flag = False  # signal to the if statement below
            composite, prime = composites[0]
            heapreplace(composites, (composite + prime*2, prime))

        if prime_flag:
            yield candidate
            heappush(composites, (candidate**2, candidate))
In [44]: from itertools import islice

In [45]: %timeit list(islice(dict_sieve(), 100000))
    ...: %timeit list(islice(heap_sieve(), 100000))
    ...: %timeit eager_sieve(1299710)  # 1299709 is the 100,000th prime
    ...: 
1 loops, best of 3: 2.12 s per loop
1 loops, best of 3: 4.94 s per loop
1 loops, best of 3: 677 ms per loop