Современный, высокопроизводительный фильтр bloom в Python?

Я ищу реализацию фильтра цветения качества продукции в Python для обработки довольно большого количества элементов (скажем, от 100M до 1B с 0,01% ложноположительной скоростью).

Pybloom является одним из вариантов, но, похоже, показывает свой возраст, поскольку он регулярно выдает ошибки предупреждения об устаревании на Python 2.5. Джо Грегорио также имеет реализация.

требования - быстрая производительность и стабильность поиска. Я также открыт для создание интерфейсов Python для особенно хороших реализаций c/C++ или даже для Jython, если есть хорошая реализация Java.

отсутствие этого, любые рекомендации по представлению битового массива / битового вектора, которые могут обрабатывать ~16E9 бит?

6 ответов


недавно я тоже пошел по этому пути; хотя похоже, что мое приложение немного отличается. Мне было интересно аппроксимировать операции множества на большом количестве строк.

вы делаете ключевое замечание, что a быстро требуется битовый вектор. В зависимости от того, что вы хотите поместить в фильтр bloom, вам также может потребоваться подумать о скорости используемого алгоритма(ов) хэширования. Вы можете найти это библиотека полезное. Вы также можете хотите возиться с методом случайных чисел, используемым ниже, который только хэширует ваш ключ один раз.

С точки зрения реализаций битового массива не Java:

Я построил свой фильтр цветения, используя BitVector. Я провел некоторое время, профилируя и оптимизируя библиотеку и внося свои исправления в Avi. Идти к тем BitVector ссылка и прокрутите вниз до подтверждения в v1.5 чтобы увидеть детали. В конце концов, я понял, что производительность не является целью этого проекта и решил не использовать его.

вот какой-то код, который у меня был. Я могу поместить это в код google на 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

кроме того, в моем случае я нашел полезным иметь более быструю функцию count_bits для BitVector. Поместите этот код в 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]

в ответ на Parand, говоря: "общая практика, похоже, использует что-то вроде SHA1 и разделяет биты, чтобы сформировать несколько хэшей", хотя это может быть правдой в том смысле, что это обычная практика (PyBloom также использует ее), это все еще не означает, что это правильно; -)

для фильтра Bloom единственное требование хэш-функции заключается в том, что ее выходное пространство должно быть равномерно распределено с учетом ожидаемого ввода. Хотя криптографический хэш, безусловно, выполняет это требование, это также немного похоже на стрельбу мухой с базукой.

попробуйте FNV Hash который использует только один XOR и одно умножение на входной байт, которое я оцениваю в несколько сотен раз быстрее, чем SHA1:)

хэш FNV не является криптографически безопасным, но вам это не нужно. Он имеет немного несовершенное поведение лавины, но вы не используете его для проверки целостности либо.

о равномерности, обратите внимание, что вторая ссылка только сделала тест Хи-квадрат для 32-битного хэша FNV. Лучше использовать больше битов и вариант FNV-1, который меняет местами шаги XOR и MUL для лучшей битовой дисперсии. Для фильтра Bloom есть еще несколько уловов, таких как равномерное отображение выходных данных в диапазон индексов битового массива. Если возможно, я бы сказал, округлить размер битового массива до ближайшей степени 2 и настроить k соответственно. Таким образом, вы получите лучшую точность, и вы можете использовать простой XOR-складывание для отображения диапазона.

кроме того, вот ссылка, объясняющая, почему вы не хотите SHA1 (или любой криптографический хэш), когда вам нужно общего назначения хэш -.


посмотреть массив модуль.

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 операции можно заменить >>3 и &0x07. Это мая привести к немного лучшей скорости с риском некоторой неясности.

кроме того, изменение 'B' и 8 to 'L' и 32 должно быть быстрее на большинстве аппаратных средств. [Переход на 'H' и 16 может быть быстрее на некоторых аппаратных средствах, но это сомнительно.]


В конце концов я нашел pybloomfiltermap. Я им не пользовался, но, похоже,он подойдет.


Я поставил реализацию фильтра Python bloom в http://stromberg.dnsalias.org / ~strombrg / drs-bloom-фильтр/

это в чистом python, имеет хорошие хэш-функции, хорошие автоматические тесты, выбор бэкэндов (диск, массив, mmap, больше) и более интуитивные аргументы для __init__ метод, поэтому вы можете указать идеальное количество элементов и желаемую максимальную частоту ошибок вместо несколько эфирных, специфичных для datastructure перестроек.


меня очень интересуют варианты фильтров Bloom, их производительность и понимание их вариантов использования. Существует так много хорошо цитируемых исследовательских работ по вариантам фильтров Bloom (в том числе опубликованных на первоклассных конференциях,таких как SIGCOMM, SIGMETRICS), но я не думаю, что их присутствие сильно в основных языковых библиотеках. Почему вы думаете, что это так?

в то время как мой интерес является языковым агностиком, я хотел поделиться статьей, которую я написал о вариантах фильтра Bloom, и применения фильтра цветения.

http://appolo85.wordpress.com/2010/08/03/bloom-filter/

Я хотел бы узнать больше об их использовании вариантов фильтров Bloom, их дизайне/реализации и библиотеках на других языках.

Как вы думаете, что большинство публикаций и (код?) на вариантах фильтров Блума служат только для увеличения количества опубликованных работ выпускника PhD?
Или дело в том, что большинство людей не хотите возиться с готовой к производству стандартной реализацией фильтра bloom, которая "работает просто отлично": D