Современный высокопроизводительный фильтр Блума в Python? - PullRequest
50 голосов
/ 22 ноября 2008

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

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

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

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

Ответы [ 6 ]

28 голосов
/ 23 ноября 2008

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

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

В терминах реализаций битовых массивов не-Java:

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

Вот некоторый код, который я лежал вокруг. Я могу поставить это на код Google в Python-Блум. Предложения приветствуются.

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]
24 голосов
/ 08 ноября 2010

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

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

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

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

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

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

10 голосов
/ 02 августа 2010

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

8 голосов
/ 22 ноября 2008

Посмотрите на массив .

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 может быть быстрее на некотором оборудовании, но это сомнительно.]

2 голосов
/ 21 августа 2012

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

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

0 голосов
/ 03 августа 2010

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

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

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

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

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

...