Skip to content

Commit b51e77b

Browse files
committed
Merge branch 'Vagabond-adt/forgetful-bloom'
2 parents eb78f6a + cdbde89 commit b51e77b

4 files changed

Lines changed: 230 additions & 2 deletions

File tree

crates/bloom/src/lib.rs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,41 @@ struct FilterResource {
2222
filter: RwLock<Bloom<[u8]>>,
2323
}
2424

25+
struct ForgetfulFilter {
26+
filter: Vec<Bloom<[u8]>>,
27+
rotate_at: usize,
28+
insertion_count: usize
29+
}
30+
31+
struct ForgetfulFilterResource {
32+
filter: RwLock<ForgetfulFilter>,
33+
}
34+
35+
2536
rustler_export_nifs!(
2637
"bloom",
2738
[
2839
("new", 2, new),
2940
("new_for_fp_rate", 2, new_for_fp_rate),
41+
("new_forgetful", 4, new_forgetful),
42+
("new_forgetful_for_fp_rate", 4, new_forgetful_for_fp_rate),
3043
("serialize", 1, serialize),
3144
("deserialize", 7, deserialize),
3245
("set", 2, set),
46+
("set_forgetful", 2, set_forgetful),
3347
("check_nif", 2, check),
3448
("check_nif", 8, check_ro),
49+
("check_forgetful", 2, check_forgetful),
3550
("check_and_set", 2, check_and_set),
3651
("clear", 1, clear),
52+
("clear_forgetful", 1, clear_forgetful),
3753
],
3854
Some(on_load)
3955
);
4056

4157
fn on_load<'a>(env: Env<'a>, _load_info: Term<'a>) -> bool {
4258
resource_struct_init!(FilterResource, env);
59+
resource_struct_init!(ForgetfulFilterResource, env);
4360
true
4461
}
4562

@@ -65,6 +82,52 @@ fn new_for_fp_rate<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult<Term<'a>> {
6582
Ok((atoms::ok(), resource).encode(env))
6683
}
6784

85+
fn new_forgetful<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult<Term<'a>> {
86+
let bitmap_size: i64 = args[0].decode()?;
87+
let items_count: i64 = args[1].decode()?;
88+
let capacity: usize = args[2].decode()?;
89+
let rotate_at: usize = args[3].decode()?;
90+
91+
let mut forgetfulfilter = ForgetfulFilter {
92+
filter: Vec::with_capacity(capacity),
93+
rotate_at: rotate_at,
94+
insertion_count: 0
95+
};
96+
97+
for _x in 0..capacity {
98+
forgetfulfilter.filter.push(Bloom::new(bitmap_size as usize, items_count as usize))
99+
}
100+
101+
let resource = ResourceArc::new(ForgetfulFilterResource {
102+
filter: RwLock::new(forgetfulfilter),
103+
});
104+
105+
Ok((atoms::ok(), resource.encode(env)).encode(env))
106+
}
107+
108+
fn new_forgetful_for_fp_rate<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult<Term<'a>> {
109+
let items_count: i64 = args[0].decode()?;
110+
let fp_p: f64 = args[1].decode()?;
111+
let capacity: usize = args[2].decode()?;
112+
let rotate_at: usize = args[3].decode()?;
113+
114+
let mut forgetfulfilter = ForgetfulFilter {
115+
filter: Vec::with_capacity(capacity),
116+
rotate_at: rotate_at,
117+
insertion_count: 0
118+
};
119+
120+
for _x in 0..capacity {
121+
forgetfulfilter.filter.push(Bloom::new_for_fp_rate(items_count as usize, fp_p))
122+
}
123+
124+
let resource = ResourceArc::new(ForgetfulFilterResource {
125+
filter: RwLock::new(forgetfulfilter),
126+
});
127+
128+
Ok((atoms::ok(), resource.encode(env)).encode(env))
129+
}
130+
68131
fn serialize<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult<Term<'a>> {
69132
let resource: ResourceArc<FilterResource> = args[0].decode()?;
70133

@@ -122,6 +185,47 @@ fn set<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult<Term<'a>> {
122185
Ok(atoms::ok().encode(env))
123186
}
124187

188+
fn set_forgetful<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult<Term<'a>> {
189+
let resource: ResourceArc<ForgetfulFilterResource> = args[0].decode()?;
190+
let key: Binary = if args[1].is_binary() {
191+
args[1].decode()?
192+
} else {
193+
Binary::from_owned(args[1].to_binary(), env)
194+
};
195+
196+
let mut filter = resource.filter.write().unwrap();
197+
198+
// check membership
199+
let mut member = false;
200+
// check the overlapping blooms 2 by 2
201+
for x in 0..filter.filter.len() - 2 {
202+
if filter.filter[x].check(&key) && filter.filter[x+1].check(&key) {
203+
member = true;
204+
break;
205+
}
206+
}
207+
if !member {
208+
// check last bloom
209+
member = filter.filter[filter.filter.len() - 1].check(&key);
210+
}
211+
212+
if !member {
213+
filter.insertion_count+=1;
214+
if filter.insertion_count >= filter.rotate_at {
215+
filter.insertion_count = 0;
216+
// rotate the oldest bloom to the start of the list
217+
// and clear it
218+
filter.filter.rotate_right(1);
219+
filter.filter[0].clear();
220+
}
221+
// set in the future and current
222+
filter.filter[0].set(&key);
223+
filter.filter[1].set(&key);
224+
}
225+
226+
Ok(member.encode(env))
227+
}
228+
125229
fn check<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult<Term<'a>> {
126230
let resource: ResourceArc<FilterResource> = args[0].decode()?;
127231
let key: Binary = if args[1].is_binary() {
@@ -135,6 +239,26 @@ fn check<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult<Term<'a>> {
135239
Ok(filter.check(&key).encode(env))
136240
}
137241

242+
fn check_forgetful<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult<Term<'a>> {
243+
let resource: ResourceArc<ForgetfulFilterResource> = args[0].decode()?;
244+
let key: Binary = if args[1].is_binary() {
245+
args[1].decode()?
246+
} else {
247+
Binary::from_owned(args[1].to_binary(), env)
248+
};
249+
250+
let filter = resource.filter.read().unwrap();
251+
252+
// check the overlapping blooms 2 by 2
253+
for x in 0..filter.filter.len() - 2 {
254+
if filter.filter[x].check(&key) && filter.filter[x+1].check(&key) {
255+
return Ok(true.encode(env))
256+
}
257+
}
258+
// check last bloom
259+
return Ok(filter.filter[filter.filter.len() - 1].check(&key).encode(env))
260+
}
261+
138262
// check a serialized bloom for key membership without fully deserializing the bloom
139263
// specifically we want to avoid the very slow bitvec deserialization and simply compute
140264
// the hash keys manually and check them inside the Erlang binary by hand
@@ -204,3 +328,15 @@ fn clear<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult<Term<'a>> {
204328

205329
Ok(atoms::ok().encode(env))
206330
}
331+
332+
fn clear_forgetful<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult<Term<'a>> {
333+
let resource: ResourceArc<ForgetfulFilterResource> = args[0].decode()?;
334+
335+
let mut filter = resource.filter.write().unwrap();
336+
for x in 0..filter.filter.len() -1 {
337+
filter.filter[x].clear();
338+
}
339+
filter.insertion_count = 0;
340+
341+
Ok(atoms::ok().encode(env))
342+
}

src/bloom.erl

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@
55
%% API
66
-export([new/2,
77
new_for_fp_rate/2,
8+
new_forgetful/4,
9+
new_forgetful_for_fp_rate/4,
810
serialize/1,
911
deserialize/1,
1012
deserialize/7,
1113
to_bin/1,
1214
from_bin/1,
1315
set/2,
16+
set_forgetful/2,
1417
check/2,
18+
check_forgetful/2,
1519
check_and_set/2,
16-
clear/1
20+
clear/1,
21+
clear_forgetful/1
1722
]).
1823

1924
%% Native library support
@@ -41,6 +46,14 @@ new(_BitmapSize, _ItemsCount) ->
4146
new_for_fp_rate(_ItemsCount, _FP_Rate) ->
4247
not_loaded(?LINE).
4348

49+
%% @private
50+
new_forgetful(_BitmapSize, _ItemCount, _Capacity, _RotateAfter) ->
51+
not_loaded(?LINE).
52+
53+
%% @private
54+
new_forgetful_for_fp_rate(_ItemCount, _FalsePositiveRate, _Capacity, _RotateAfter) ->
55+
not_loaded(?LINE).
56+
4457
%% @doc Serialize a bloom filter to Erlang terms. `check/2' can be used against this serialized form efficently.
4558
-spec serialize(Bloom :: bloom()) -> {ok, serialized_bloom()}.
4659
serialize(_Ref) ->
@@ -75,6 +88,10 @@ deserialize(_Bitmap, _NumBits, _NumFuns, _Sv00, _Sv01, _Sv10, _Sv11) ->
7588
set(_Ref, _Key) ->
7689
not_loaded(?LINE).
7790

91+
%% @private
92+
set_forgetful(_Bloom, _Key) ->
93+
not_loaded(?LINE).
94+
7895
%% @doc Check for the presence of `Key' in `Bloom'.
7996
%% Serialized and binary encoded bloom filters can be used with this
8097
%% function when you wish to check for the key and do not need to use set
@@ -89,6 +106,10 @@ check(<<?ERBLOOM_VERSION1:8/integer, NumBits:64/integer-unsigned-little, NumFuns
89106
check({Bitmap,NumBits,NumFuns,{Sv00,Sv01},{Sv10,Sv11}}, Key) ->
90107
check_nif(Bitmap, NumBits, NumFuns, Sv00, Sv01, Sv10, Sv11, Key).
91108

109+
%% @private
110+
check_forgetful(_Bloom, _Key) ->
111+
not_loaded(?LINE).
112+
92113
%% @doc Record the presence of `Key' in `Bloom' and return whether it was present before.
93114
-spec check_and_set(Bloom :: bloom(), Key :: term()) -> boolean().
94115
check_and_set(_Ref, _Key) ->
@@ -99,6 +120,10 @@ check_and_set(_Ref, _Key) ->
99120
clear(_Ref) ->
100121
not_loaded(?LINE).
101122

123+
%% @private
124+
clear_forgetful(_Ref) ->
125+
not_loaded(?LINE).
126+
102127
check_nif(_Ref, _Key) ->
103128
not_loaded(?LINE).
104129

src/forgetful_bloom.erl

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
%% @doc This is an implementation of Forgetful Bloom Filters [http://dprg.cs.uiuc.edu/docs/fbf_cac15/fbfpaper-2.pdf] built on top of [https://crates.io/crates/bloomfilter]
2+
3+
-module(forgetful_bloom).
4+
%% API
5+
-export([new/4,
6+
new_for_fp_rate/4,
7+
set/2,
8+
check/2,
9+
clear/1
10+
]).
11+
12+
13+
-opaque bloom() :: reference().
14+
-export_type([bloom/0]).
15+
16+
%% @doc Create a new forgetful bloom filter structure. `BitmapSize' is the size in bytes (not bits) that will be allocated in memory `ItemsCount' is an estimation of the maximum number of items to store, `NumFilters' is the number of filters to maintain (minimum of 3) and `RotateAfter' is how many insertions to do into a filter before rotating a blank filter into the `future' position.
17+
-spec new(BitmapSize :: pos_integer(), ItemsCount :: pos_integer(), NumFilters :: pos_integer(), RotateAfter :: pos_integer()) -> {ok, Bloom :: bloom()}.
18+
new(BitmapSize, ItemsCount, NumFilters, RotateAfter) when NumFilters > 2 ->
19+
bloom:new_forgetful(BitmapSize, ItemsCount, NumFilters, RotateAfter).
20+
21+
%% @doc Create a new forgetful bloom filter structure. `ItemsCount' is an estimation of the maximum number of items to store. `FalsePositiveRate' is the wanted rate of false positives, in ]0.0, 1.0[, `NumFilters' is the number of filters to maintain (minimum of 3) and `RotateAfter' is how many insertions to do into a filter before rotating a blank filter into the `future' position.
22+
-spec new_for_fp_rate(ItemsCount :: pos_integer(), FalsePositiveRate :: float(), NumFilters :: pos_integer(), RotateAfter :: pos_integer()) -> {ok, Bloom :: bloom()}.
23+
new_for_fp_rate(ItemsCount, FP_Rate, NumFilters, RotateAfter) ->
24+
bloom:new_forgetful_for_fp_rate(ItemsCount, FP_Rate, NumFilters, RotateAfter).
25+
26+
%% @doc Record the presence of `Key' in `ForgetfulBloom'. Like `bloom:check_and_set/2' a boolean is returned to indicate if the value was already present.
27+
%% @see bloom:check_and_set/2
28+
-spec set(ForgetfulBloom :: bloom(), Key :: term()) -> WasAlreadyPresent :: boolean().
29+
set(Ref, Key) ->
30+
bloom:set_forgetful(Ref, Key).
31+
32+
%% @doc Check for the presence of `Key' in `ForgetfulBloom'.
33+
-spec check(ForgetfulBloom :: bloom(), term()) -> boolean().
34+
check(Ref, Key) ->
35+
bloom:check_forgetful(Ref, Key).
36+
37+
%% @doc Clear all of the bits in the filter, removing all keys from the set.
38+
-spec clear(ForgetfulBloom :: bloom()) -> ok.
39+
clear(Ref) ->
40+
bloom:clear_forgetful(Ref).
41+
42+
43+

test/bloom_SUITE.erl

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ groups() ->
1414
[
1515
{bloom,
1616
[parallel, shuffle],
17-
[new, serialize, deserialize, set, check, check_and_set, clear, to_from_bin]},
17+
[new, serialize, deserialize, set, check, check_and_set, clear, to_from_bin, forgetful]},
1818

1919
{perf,
2020
[shuffle],
@@ -130,6 +130,30 @@ to_from_bin(_) ->
130130
false = bloom:check(Bin, Key2),
131131
ok.
132132

133+
forgetful(_) ->
134+
{ok, Ref} = forgetful_bloom:new(50,80,3,1),
135+
Key = <<"binkeyfortest">>,
136+
Key2 = <<"binkeyfortestingmore">>,
137+
Key3 = <<"icanseemygousefromhere">>,
138+
Key4 = <<"icantbelieveitsnotbutter">>,
139+
Key5 = <<"myhovercraftisfullofeels">>,
140+
Key6 = <<"ivefallenandicantgetup">>,
141+
Keys = [Key, Key2, Key3, Key4, Key5, Key6],
142+
[] = [K || K <- Keys, forgetful_bloom:check(Ref, K)],
143+
false = forgetful_bloom:set(Ref, Key),
144+
?assertEqual([Key], [K || K <- Keys, forgetful_bloom:check(Ref, K)]),
145+
false = forgetful_bloom:set(Ref, Key2),
146+
?assertEqual([Key, Key2], [K || K <- Keys, forgetful_bloom:check(Ref, K)]),
147+
false = forgetful_bloom:set(Ref, Key3),
148+
?assertEqual([Key, Key2, Key3], [K || K <- Keys, forgetful_bloom:check(Ref, K)]),
149+
false = forgetful_bloom:set(Ref, Key4),
150+
?assertEqual([Key2, Key3, Key4], [K || K <- Keys, forgetful_bloom:check(Ref, K)]),
151+
false = forgetful_bloom:set(Ref, Key5),
152+
?assertEqual([Key3, Key4, Key5], [K || K <- Keys, forgetful_bloom:check(Ref, K)]),
153+
false = forgetful_bloom:set(Ref, Key6),
154+
?assertEqual([Key4, Key5, Key6], [K || K <- Keys, forgetful_bloom:check(Ref, K)]),
155+
ok.
156+
133157
%% =============================================================================
134158
%% group: perf
135159
%% =============================================================================

0 commit comments

Comments
 (0)