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

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

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

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

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


person Parand    schedule 22.11.2008    source источник
comment
Можете из интереса объяснить, что не так с существующими реализациями (особенно с PyBloom)? Он может быть долго в зубе, но если он работает и не нуждается в исправлении, это звучит как плюс.   -  person Oddthinking    schedule 22.11.2008
comment
Странное мышление, обновленное с некоторыми пояснениями.   -  person Parand    schedule 22.11.2008


Ответы (6)


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

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

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

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


#
# [email protected] / 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]
person Ryan Cox    schedule 22.11.2008
comment
Спасибо, Райан, очень информативно. Что касается производительности BitVector, вы нашли более быструю альтернативу? Кроме того, я заметил, что вы используете только 4 хэша, что кажется немного низким. Есть мысли по этому поводу? Распространенной практикой является использование чего-то вроде SHA1 и разделение битов для формирования нескольких хэшей. - person Parand; 23.11.2008
comment
Hashcount зависит от: # элементов и приемлемого уровня ложных срабатываний. У меня есть улучшенная версия вышеперечисленного, которую я проверю. Не нашел ничего быстрее (хотя я предполагаю, что это будет нативная реализация). - person Ryan Cox; 27.11.2008
comment
Не могли бы вы добавить строку документации? Для чего используются эти значения n=None, m=None, k=None, p=None, bits=None? - person Ahmed; 22.01.2019

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

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

Вместо этого попробуйте хэш FNV, который использует только одно XOR и одно умножение на входной байт. , который, по моим оценкам, в несколько сотен раз быстрее, чем SHA1 :)

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

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

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

person Kazeng    schedule 08.11.2010
comment
+1, хороший ответ. И да, ограничение на размещение ссылок новыми пользователями довольно глупо. - person Scott Griffiths; 08.11.2010
comment
Спасибо, чувак, я знал, что сохранение этого двухлетнего вопроса в избранном когда-нибудь окупится! - person drxzcl; 08.11.2010
comment
Спасибо за красивый ответ. В настоящее время я не использую фильтры Блума, но если я когда-нибудь доберусь до этого, я посмотрю, смогу ли я модифицировать FNV вместо SHA1. - person Parand; 09.11.2010

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

person Parand    schedule 02.08.2010
comment
дайте мне знать, если библиотека полезна для вас! - person Mike Axiak; 16.09.2010
comment
Это самый быстрый из тех, что я смог найти. Протестировано pybloom_live, pyprobables и pybloof. Также быстрее, чем cuckoopy. pyprobables BTW невероятно медленный. Здесь очень грубый код: gist.github.com/fjsj/f7f544ebcedb1ad931a4d31cdc9d2fb5 - person fjsj; 21.01.2019

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

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

person Community    schedule 22.11.2008

Прошло почти десять лет с тех пор, как здесь были самые последние ответы. И времена меняются.

Похоже, самым популярным поддерживаемым пакетом фильтра Блума в конце 2019 года стал следующий: https://github.com/joseph-fox/python-bloomfilter, доступно на PyPi как pybloom_live: https://pypi.org/project/pybloom_live/

person Graham Lea    schedule 05.12.2019

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

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

person user1277476    schedule 21.08.2012