Boost logo

Boost :

Subject: Re: [boost] Boost.Bloom_filter now in Sandbox (WAS Re: Interest in a Bloom Filter?)
From: Dean Michael Berris (mikhailberis_at_[hidden])
Date: 2009-06-19 02:16:25


On Fri, Jun 19, 2009 at 4:59 AM, Arash Partow<arash_at_[hidden]> wrote:
> Dean Michael Berris wrote:
>>
>> Hi Arash,
>>
>> On Thu, Jun 18, 2009 at 7:00 AM, Arash Partow wrote:
>>>
>>> 1. Can you please explain why you need multiple "hashes"?
>>
>> You don't need multiple hashes, but if you do want to use more hash
>> functions (K) you can. The point is the user should be able to provide
>> the hash functions that the bloom filter will use internally.
>>
>
> Please read something other than wiki, there are many great citations on the
> wiki article that explain the most practical state of the art BFs. If you
> want something for boost then at least try and provide the most correct set
> of interfaces even if the underlying details are not the most correct.
> In short you only need 2 independent hash functions to get the optimal fpp
> for a set of size m bits(an expected insertion amount is optional).
>

When you say correct set of interfaces, what do you need more than
'insert(...)' and 'contains(...)' for the basic bloom_filter? In the
case of a counting bloom filter, maybe you'd have 'remove(...)' or
'erase(...)'. Other than these functions I don't know what you mean by
most correct set of interfaces.

>
>
>>> 2. Your code doesn't trivially exit on the contains
>>
>> It used to be that it did (in the version that didn't use
>> Boost.Fusion). I can try and optimize still in cases when it
>> encounters a '0' bit in between the hash function results.
>>
>
> this is what i see:
>
>  bool contains(const_ref input) const {
>     using fusion::for_each;
>     typedef typename base::contains_impl contains_checker;
>     bool found = true;
>     for_each(
>             hash_functions,
>             contains_checker(bit_set, input, found)
>             );
>     return found;
>  }
>
> unless the contract for for_each has changed (which it hasn't both in stl
> and fusion):
>
> 1. it doesn't exit

Oh, but it does. Have you tried the tests? Remember this is
Boost.Fusion --
http://www.boost.org/doc/libs/1_39_0/libs/fusion/doc/html/fusion/algorithm/iteration/functions/for_each.html
-- it iterates on the 'hash_functions' sequence which is finite.

> 2. if you even bother to look at the machine code generated, you'd see that
> even with the best available set of opt flags,
> its still miles behind a simple loop with a break statement. You have a copy
> constructor being called, do you know the cost that will incur when calling
> contains 10^9 times?!
>

Really? The Boost.Fusion version actually unrolls the loop for you, so
the cost of that for_each call is actually constant time. Sure,
there's no short-circuit (yet) but then the cost is constant.

>
>>> 3. Your solution doesn't provide a means to determine the various optimal
>>> parameter combinations
>>
>> What do you mean by this?
>>
>
> There are various (many) use-cases for a BF
>
> 1. user provides max size and max fpp BF determines best k
> 2. user provides max fpp and max expected insertion BF determine best k,m
> 3. user provides max fpp and fuzzy k with limit BF determines m,k and
> optimal fpp
> 4. etc etc...
>
> Some of these can change/vary during runtime, so getters for fpp and
> inserted count would be nice/good/expected.
>

This is too complex -- and has too many assumptions. First, it assumes
that the user wants a self-(re)sizing bloom filter. Second it assumes
that the user doesn't want to provide their own hash functions. Third
it assumes that the user actually really cares if the bloom filter
should be smarter than the user.

In some cases (like the simplest cases I'm trying to solve for the
moment) the current implementation seems sufficient. Of course, it's
not fancy, and I don't intend for it to become too fancy. If you think
you can implement something better, then please by all means show me
code that I can deal with -- this is not said sarcastically, I really
do want to see what you mean when you say all the above translated
into code.

>
>>> 4. Your solution doesn't provide a means for counting modes
>>
>> Modes for bloom filters? I'm not sure I understand what you mean.
>>
>
> There are modes of BFs where instead of 1 bit per block you use more say 4
> bits and instead of setting high you increment a counter.
> this allows for a probabilistic determination of how many times an element
> has been inserted into a BF, this is called a counting bloom filter.
> this is another of MANY important use-case you have not covered.
>

I thought 'counting modes' meant 'Mode' in the statistical sense and
getting these statistical modes.

I would have understood it better if you meant I wasn't implementing
counting bloom filters or bloomier filters or <insert new variation of
a highfalutin name> bloom filter.

Anyway, the point is to provide a bloom filter using the textbook
definition. If there's a need for other kinds of bloom filters, like
counting bloom filters, then that's another implementation that can be
bundled in the library later -- *if* there is enough need for it, and
*if* the library even gets into Boost just trying to solve the common
(basic) cases. Of course the library is designed to evolve.

>
>>> 5. Your solution doesn't provide for erasures (do not confuse with
>>> removal
>>> of inserted items)
>>
>> .clear() is supposed to clear the internal bitset and clear the bloom
>> filter.
>>
>
> No not clear. A bloom filter is to a degree error resilient/tolerant. if for
> some reason part of your bloom filter data is damaged (n/w io, bad memory
> etc...) you can erase that section (set all the bits high), doing so
> depending on how large the section is will increase the fpp. For some uses
> that is still quite acceptable, as the fpp has already been determined to be
> orders of magnitude less than what can be tolerated.
>

If you want to go down this road, then you think about implementing
your own allocator with error correction support and use that in the
bloom_filter or any container that supports/uses custom allocators.
Maybe you'll want an STL vector that also knows how to determine if
part of the data is corrupted too?

>
>
>>> 6. Your solution doesn't provide for self superimposed compression mode
>>> (similar to union but doubling of fpp)
>>>
>>
>> The idea of the bloom_filter is to provide precisely that, a bloom
>> filter -- any compression you might want to do in transmitting a
>> bloom_filter would IMO be beyond the scope of the library. There has
>> been some suggestions of implementing a compressed_bloom_filter that
>> maintains a compressed bitset, but afar from that I must say I have no
>> idea what self superimposed compression mode.
>>
>
> This is NOT data compression if you are providing union operations, then you
> should also provide self compression, that is for example, OR'ing the 1st
> half of the filter with the 2nd half, decreasing its size.
>

I don't understand why you want to do this when clearly the Bloom
Filter is meant to have a fixed number of bits. Any variation to this
in transit is beyond the scope of the implementation of the Bloom
Filter.

> Also another note looking at your code again (btw do you even use it?!),
> you're equality test should also include the hashes and any other seeding
> data you use to construct the BF.
>

I guess you missed the earlier discussion: the point was that since
the hash functions are part of the bloom_filter's type, it's assumed
that two bloom_filter instances of the same type have the same hash
functions. If one bloom filter has a different encapsulated state
compared to another bloom filter of the same type, then they are not
equal -- simple, and easy to understand/implement. That's the point of
equality testing after all.

> Another thing that is missing is serialization of the BF, I would like to be
> able to save to file, or send it over a n/w. So a streaming operator or
> something inline with boost.serialize would be good.
>

Yes, I know. And if you missed the earlier discussion, I'm working on it. ;-)

> Another thing to consider is that a BF implementation should not be a means
> to show-off one's abilities in the use of templates and meta-programming
> paradigms but rather something that is efficient and lean. Think about
> situations where cache hit-miss and coherency will be an issue (as these are
> some of the more critical uses of BFs), look at the machine code that is
> generated for some of the more critical routines insert/contains for various
> archs and ask yourself, what it is you're "really" trying to achieve.
>

Hmmm... I guess you haven't followed the discussion at all in the list.

Let me rehash a bit:

1. The only reason Boost.Fusion was used was because the hash
functions were actually made part of the type (of the bloom_filter
template). There was a need to iterate through the hash functions when
inserting and checking an element whether they're part of the
represented set.
2. The reason there are two bloom filter implementations was because
there was a need for a version that was able to construct a bitset of
size M at runtime (bloom_filter) as opposed to one that had the size
set as part of the type (static_bloom_filter).
3. Nobody is showing-off one's abilities to use templates and
meta-programming here: the point for using Boost.Fusion is to simplify
the iteration of the sequence of Hash Functions. If there was any
showing-off that that was going to happen with template
metaprogramming by me, I wouldn't have used Boost.Fusion and had done
it by hand instead.
4. The implementation is meant to be simple, portable across
platforms, and C++ standard-compliant. When you deal with these
issues, it's best you don't think about cache hit-miss and coherency
issues IMO. At any rate if you want to have better control of the
memory allocation and issues like these, you can control them by: a)
using your favorite STL implementation of an std::bitset in the case
of the statically-sized filter or b) by providing your own allocator
and using the "correct" block type in the case of a
runtime-constructed Bloom Filter.

Of course, if the library under construction isn't suiting your needs,
there should be room for improvement. But there's no need to be too
fancy with the implementation/interface and being clever with the
False Positive Probability -- you can control this easily with
choosing/implementing your own hash functions and the size of the
underlying bitset.

HTH

-- 
Dean Michael Berris | Software Engineer, Friendster, Inc.
blog.cplusplus-soup.com | twitter.com/mikhailberis |
linkedin.com/in/mikhailberis | profiles.friendster.com/mikhailberis |
deanberris.com

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk