Python 中的现代、高性能布隆过滤器? [关闭]

Posted

技术标签:

【中文标题】Python 中的现代、高性能布隆过滤器? [关闭]【英文标题】:Modern, high performance bloom filter in Python? [closed] 【发布时间】:2010-09-23 14:18:58 【问题描述】:

我正在寻找 Python 中的生产质量布隆过滤器实现来处理相当大量的项目(比如 1 亿到 1B 个项目,误报率为 0.01%)。

Pybloom 是一种选择,但它似乎正在显示其年龄,因为它定期在 Python 2.5 上引发 DeprecationWarning 错误。 Joe Gregorio 也有an implementation。

要求是快速查找性能和稳定性。我也愿意为特别好的 c/c++ 实现创建 Python 接口,如果有好的 Java 实现,甚至可以为 Jython 创建接口。

缺乏这些,关于可以处理 ~16E9 位的位数组/位向量表示的任何建议?

【问题讨论】:

出于兴趣,您能解释一下现有实现(尤其是 PyBloom)有什么问题吗?它可能“长在牙齿上”,但如果它有效并且不需要修复,那听起来像是一个加号。 奇怪的想法,更新了一些解释。 【参考方案1】:

查看array 模块。

class Bit( object ):
    def __init__( self, size ):
        self.bits= array.array('B',[0 for i in range((size+7)//8)] )
    def set( self, bit ):
        b= self.bits[bit//8]
        self.bits[bit//8] = b | 1 << (bit % 8)
    def get( self, bit ):
        b= self.bits[bit//8]
        return (b >> (bit % 8)) & 1

FWIW,所有//8% 8 操作都可以替换为&gt;&gt;3&amp;0x07。这可能会导致速度稍快,但可能会有些模糊。

此外,将'B'8 更改为'L'32 在大多数硬件上应该更快。 [更改为'H' 和 16 在某些硬件上可能会更快,但值得怀疑。]

【讨论】:

【参考方案2】:

我最近也走上了这条路;虽然听起来我的应用程序略有不同。我对在大量字符串上近似集合操作很感兴趣。

您确实注意到需要快速位向量。根据您想要放入布隆过滤器的内容,您可能还需要考虑所使用的散列算法的速度。您可能会发现这个library 很有用。您可能还想修改下面使用的随机数技术,该技术仅对您的密钥进行一次哈希处理。

就非Java位数组实现而言:

Boost 有dynamic_bitset Java 有内置的BitSet

我使用BitVector 构建了我的布隆过滤器。我花了一些时间分析和优化库,并将我的补丁回馈给 Avi。转到该 BitVector 链接并向下滚动到 v1.5 中的致谢以查看详细信息。最后,我意识到性能不是这个项目的目标,并决定不使用它。

这里有一些我躺在那里的代码。我可以把它放在 python-bloom 的谷歌代码上。欢迎提出建议。

from BitVector import BitVector
from random import Random
# get hashes from http://www.partow.net/programming/hashfunctions/index.html
from hashes import RSHash, JSHash, PJWHash, ELFHash, DJBHash


#
# ryan.a.cox@gmail.com / www.asciiarmor.com
#
# copyright (c) 2008, ryan cox
# all rights reserved 
# BSD license: http://www.opensource.org/licenses/bsd-license.php
#

class BloomFilter(object):
    def __init__(self, n=None, m=None, k=None, p=None, bits=None ):
        self.m = m
        if k > 4 or k < 1:
            raise Exception('Must specify value of k between 1 and 4')
        self.k = k
        if bits:
            self.bits = bits
        else:
            self.bits = BitVector( size=m )
        self.rand = Random()
        self.hashes = []
        self.hashes.append(RSHash)
        self.hashes.append(JSHash)
        self.hashes.append(PJWHash)
        self.hashes.append(DJBHash)

        # switch between hashing techniques
        self._indexes = self._rand_indexes
        #self._indexes = self._hash_indexes

    def __contains__(self, key):
        for i in self._indexes(key): 
            if not self.bits[i]:
                return False    
        return True 

    def add(self, key):
        dupe = True 
        bits = []
        for i in self._indexes(key): 
            if dupe and not self.bits[i]:
                dupe = False
            self.bits[i] = 1
            bits.append(i)
        return dupe

    def __and__(self, filter):
        if (self.k != filter.k) or (self.m != filter.m): 
            raise Exception('Must use bloom filters created with equal k / m paramters for bitwise AND')
        return BloomFilter(m=self.m,k=self.k,bits=(self.bits & filter.bits))

    def __or__(self, filter):
        if (self.k != filter.k) or (self.m != filter.m): 
            raise Exception('Must use bloom filters created with equal k / m paramters for bitwise OR')
        return BloomFilter(m=self.m,k=self.k,bits=(self.bits | filter.bits))

    def _hash_indexes(self,key):
        ret = []
        for i in range(self.k):
            ret.append(self.hashes[i](key) % self.m)
        return ret

    def _rand_indexes(self,key):
        self.rand.seed(hash(key))
        ret = []
        for i in range(self.k):
            ret.append(self.rand.randint(0,self.m-1))
        return ret

if __name__ == '__main__':
    e = BloomFilter(m=100, k=4)
    e.add('one')
    e.add('two')
    e.add('three')
    e.add('four')
    e.add('five')        

    f = BloomFilter(m=100, k=4)
    f.add('three')
    f.add('four')
    f.add('five')
    f.add('six')
    f.add('seven')
    f.add('eight')
    f.add('nine')
    f.add("ten")        

    # test check for dupe on add
    assert not f.add('eleven') 
    assert f.add('eleven') 

    # test membership operations
    assert 'ten' in f 
    assert 'one' in e 
    assert 'ten' not in e 
    assert 'one' not in f         

    # test set based operations
    union = f | e
    intersection = f & e

    assert 'ten' in union
    assert 'one' in union 
    assert 'three' in intersection
    assert 'ten' not in intersection
    assert 'one' not in intersection

另外,就我而言,我发现为 BitVector 提供更快的 count_bits 函数很有用。将此代码放入 BitVector 1.5 中,它应该会为您提供更高效的位计数方法:

def fast_count_bits( self, v ):
    bits = (
            0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
            1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
            1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
            1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
            3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
            4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 )

    return bits[v & 0xff] + bits[(v >> 8) & 0xff] + bits[(v >> 16) & 0xff] + bits[v >> 24]

【讨论】:

感谢瑞恩,非常有用。关于 BitVector 的性能,您是否找到了更快的替代方案?另外,我注意到您只使用了 4 个哈希,这似乎有点低。对此有什么想法吗?一种常见的做法似乎是使用 SHA1 之类的东西并将位拆分以形成多个哈希。 哈希计数取决于:# 个元素和可接受的误报率。我有上面的改进版本,我将签入。还没有找到更快的东西(尽管我想这将是一个原生实现)。 你能添加一个文档字符串吗?这些值n=None, m=None, k=None, p=None, bits=None 用于什么?【参考方案3】:

最终我找到了pybloomfiltermap。我没用过,但看起来很合算。

【讨论】:

如果图书馆对你有用,请告诉我! 这是我能找到的最快的。测试了 pybloom_live、pyprobables 和 pybloof。也比 cuckoopy 快。 pyprobables BTW 非常慢。此处代码非常粗略:gist.github.com/fjsj/f7f544ebcedb1ad931a4d31cdc9d2fb5【参考方案4】:

作为对 Parand 的回应,他说“常见做法似乎是使用 SHA1 之类的东西并将位拆分以形成多个散列”,虽然从某种意义上说这是常见做法(PyBloom 也使用它),这可能是正确的,它仍然不意味着这是正确的做法;-)

对于布隆过滤器,散列函数的唯一要求是它的输出空间必须在给定预期输入的情况下均匀分布。虽然加密哈希当然可以满足这一要求,但它也有点像用火箭筒射击苍蝇。

试试FNV Hash,它每个输入字节只使用一个 XOR 和一个乘法,我估计它比 SHA1 快几百倍:)

FNV 哈希在密码学上并不安全,但您不需要这样做。它有一点imperfect avalanche behaviour,但你也没有将它用于完整性检查。

关于一致性,请注意第二个链接仅对 32 位 FNV 散列进行卡方检验。最好使用更多位和 FNV-1 变体,它交换 XOR 和 MUL 步骤以获得更好的位分散。对于布隆过滤器,还有一些捕获,例如将输出统一映射到位数组的索引范围。如果可能的话,我会说将位数组的大小四舍五入到最接近的 2 次幂并相应地调整 k。这样您可以获得更高的准确性,并且可以使用简单的 XOR 折叠来映射范围。

此外,这里有一个参考解释了为什么在需要 a general purpose hash 时不需要 SHA1(或任何加密哈希)。

【讨论】:

+1,很好的答案。是的,对新用户发布链接的限制非常愚蠢。 谢谢伙计,我知道保留这个两年前的问题总有一天会得到回报! 感谢您的精彩回答。我目前没有使用布隆过滤器,但如果我能解决它,我会看看我是否可以在那里改装 FNV 而不是 SHA1。【参考方案5】:

我已经在http://stromberg.dnsalias.org/~strombrg/drs-bloom-filter/ 提出了一个 python 布隆过滤器实现

它是纯 python,具有良好的哈希函数、良好的自动化测试、后端选择(磁盘、数组、mmap 等)和__init__ 方法的更直观的参数,因此您可以指定理想的元素数量和期望的最大错误率,而不是一些空灵的、特定于数据结构的可调参数。

【讨论】:

【参考方案6】:

距离最近的答案已经过去了将近十年。时代确实在变。

看起来 2019 年末最流行的维护布隆过滤器包现在是这个:https://github.com/joseph-fox/python-bloomfilter,在 PyPi 上可作为 pybloom_live:https://pypi.org/project/pybloom_live/

【讨论】:

以上是关于Python 中的现代、高性能布隆过滤器? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

HashMap和布隆过滤器命中性能测试

Spark布隆过滤器(bloomFilter)

Python爬虫学习——布隆过滤器

Hbase的Bloomfilter(布隆过滤器)

布隆过滤器过时了,未来属于布谷鸟过滤器?

python 布隆过滤器