Skip to content

Commit c77f01d

Browse files
committed
Update
1 parent fb1d06a commit c77f01d

File tree

4 files changed

+142
-47
lines changed

4 files changed

+142
-47
lines changed

benchmarks/bulk-insert-and-query.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ typedef struct samples samples_t;
156156

157157
template <typename Table>
158158
Statistics FilterBenchmark(
159-
size_t add_count, const vector<uint64_t>& to_add,
159+
size_t add_count, vector<uint64_t> to_add,
160160
size_t intersectionsize, const std::vector<samples_t> & mixed_sets, bool batchedadd = false, bool remove = false) {
161161
if (add_count > to_add.size()) {
162162
throw out_of_range("to_add must contain at least add_count values");

benchmarks/filterapi.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,7 @@ class XorSingle {
926926
}
927927
}
928928
~XorSingle() { xor8_free(&filter); }
929-
bool AddAll(const uint64_t *data, const size_t start, const size_t end) {
929+
bool AddAll(uint64_t *data, const size_t start, const size_t end) {
930930
return xor8_buffered_populate(data + start, end - start, &filter);
931931
}
932932
inline bool Contain(uint64_t &item) const {
@@ -949,7 +949,7 @@ template <> struct FilterAPI<XorSingle> {
949949
static void Add(uint64_t, Table *) {
950950
throw std::runtime_error("Unsupported");
951951
}
952-
static void AddAll(const vector<uint64_t> &keys, const size_t start,
952+
static void AddAll(vector<uint64_t> &keys, const size_t start,
953953
const size_t end, Table *table) {
954954
table->AddAll(keys.data(), start, end);
955955
}
@@ -971,7 +971,7 @@ class BinaryFuseSingle {
971971
}
972972
}
973973
~BinaryFuseSingle() { binary_fuse8_free(&filter); }
974-
bool AddAll(const uint64_t *data, const size_t start, const size_t end) {
974+
bool AddAll(uint64_t *data, const size_t start, const size_t end) {
975975
return binary_fuse8_populate(data + start, end - start, &filter);
976976
}
977977
inline bool Contain(uint64_t &item) const {
@@ -996,7 +996,7 @@ template <> struct FilterAPI<BinaryFuseSingle> {
996996
static void Add(uint64_t, Table *) {
997997
throw std::runtime_error("Unsupported");
998998
}
999-
static void AddAll(const vector<uint64_t> &keys, const size_t start,
999+
static void AddAll(vector<uint64_t> &keys, const size_t start,
10001000
const size_t end, Table *table) {
10011001
table->AddAll(keys.data(), start, end);
10021002
}

src/xorfilter/binaryfusefilter_singleheader.h

Lines changed: 92 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,22 @@
1313
// highly unlikely
1414
#endif
1515

16+
static int binary_fuse_cmpfunc(const void * a, const void * b) {
17+
return ( *(const uint64_t*)a - *(const uint64_t*)b );
18+
}
19+
20+
static size_t binary_fuse_sort_and_remove_dup(uint64_t* keys, size_t length) {
21+
qsort(keys, length, sizeof(uint64_t), binary_fuse_cmpfunc);
22+
size_t j = 0;
23+
for(size_t i = 1; i < length; i++) {
24+
if(keys[i] != keys[i-1]) {
25+
keys[j] = keys[i];
26+
j++;
27+
}
28+
}
29+
return j+1;
30+
}
31+
1632
/**
1733
* We start with a few utilities.
1834
***/
@@ -60,13 +76,73 @@ typedef struct binary_fuse8_s {
6076
uint8_t *Fingerprints;
6177
} binary_fuse8_t;
6278

63-
#ifdef _MSC_VER
64-
// Windows programmers who target 32-bit platform may need help:
65-
static inline uint64_t binary_fuse_mulhi(uint64_t a, uint64_t b) { return __umulh(a, b); }
66-
#else
79+
// #ifdefs adapted from:
80+
// https://stackoverflow.com/a/50958815
81+
#ifdef __SIZEOF_INT128__ // compilers supporting __uint128, e.g., gcc, clang
6782
static inline uint64_t binary_fuse_mulhi(uint64_t a, uint64_t b) {
6883
return ((__uint128_t)a * b) >> 64;
6984
}
85+
#elif defined(_M_X64) || defined(_MARM64) // MSVC
86+
static inline uint64_t binary_fuse_mulhi(uint64_t a, uint64_t b) {
87+
return __umulh(a, b);
88+
}
89+
#elif defined(_M_IA64) // also MSVC
90+
static inline uint64_t binary_fuse_mulhi(uint64_t a, uint64_t b) {
91+
unsigned __int64 hi;
92+
(void) _umul128(a, b, &hi);
93+
return hi;
94+
}
95+
#else // portable implementation using uint64_t
96+
static inline uint64_t binary_fuse_mulhi(uint64_t a, uint64_t b) {
97+
// Adapted from:
98+
// https://stackoverflow.com/a/51587262
99+
100+
/*
101+
This is implementing schoolbook multiplication:
102+
103+
a1 a0
104+
X b1 b0
105+
-------------
106+
00 LOW PART
107+
-------------
108+
00
109+
10 10 MIDDLE PART
110+
+ 01
111+
-------------
112+
01
113+
+ 11 11 HIGH PART
114+
-------------
115+
*/
116+
117+
const uint64_t a0 = (uint32_t) a;
118+
const uint64_t a1 = a >> 32;
119+
const uint64_t b0 = (uint32_t) b;
120+
const uint64_t b1 = b >> 32;
121+
const uint64_t p11 = a1 * b1;
122+
const uint64_t p01 = a0 * b1;
123+
const uint64_t p10 = a1 * b0;
124+
const uint64_t p00 = a0 * b0;
125+
126+
// 64-bit product + two 32-bit values
127+
const uint64_t middle = p10 + (p00 >> 32) + (uint32_t) p01;
128+
129+
/*
130+
Proof that 64-bit products can accumulate two more 32-bit values
131+
without overflowing:
132+
133+
Max 32-bit value is 2^32 - 1.
134+
PSum = (2^32-1) * (2^32-1) + (2^32-1) + (2^32-1)
135+
= 2^64 - 2^32 - 2^32 + 1 + 2^32 - 1 + 2^32 - 1
136+
= 2^64 - 1
137+
Therefore the high half below cannot overflow regardless of input.
138+
*/
139+
140+
// high half
141+
return p11 + (middle >> 32) + (p01 >> 32);
142+
143+
// low half (which we don't care about, but here it is)
144+
// (middle << 32) | (uint32_t) p00;
145+
}
70146
#endif
71147

72148
typedef struct binary_hashes_s {
@@ -151,7 +227,7 @@ static inline bool binary_fuse8_allocate(uint32_t size,
151227
filter->SegmentLength = 262144;
152228
}
153229
filter->SegmentLengthMask = filter->SegmentLength - 1;
154-
double sizeFactor = binary_fuse_calculate_size_factor(arity, size);
230+
double sizeFactor = size <= 1 ? 0 : binary_fuse_calculate_size_factor(arity, size);
155231
uint32_t capacity = size <= 1 ? 0 : (uint32_t)(round((double)size * sizeFactor));
156232
uint32_t initSegmentCount =
157233
(capacity + filter->SegmentLength - 1) / filter->SegmentLength -
@@ -197,7 +273,7 @@ static inline uint8_t binary_fuse_mod3(uint8_t x) {
197273
// The caller is responsable for calling binary_fuse8_allocate(size,filter)
198274
// before. For best performance, the caller should ensure that there are not too
199275
// many duplicated keys.
200-
static inline bool binary_fuse8_populate(const uint64_t *keys, uint32_t size,
276+
static inline bool binary_fuse8_populate(uint64_t *keys, uint32_t size,
201277
binary_fuse8_t *filter) {
202278
uint64_t rng_counter = 0x726b2b9d438b9d4d;
203279
filter->Seed = binary_fuse_rng_splitmix64(&rng_counter);
@@ -230,17 +306,15 @@ static inline bool binary_fuse8_populate(const uint64_t *keys, uint32_t size,
230306
for (int loop = 0; true; ++loop) {
231307
if (loop + 1 > XOR_MAX_ITERATIONS) {
232308
// The probability of this happening is lower than the
233-
// the cosmic-ray probability (i.e., a cosmic ray corrupts your system),
234-
// but if it happens, we just fill the fingerprint with ones which
235-
// will flag all possible keys as 'possible', ensuring a correct result.
309+
// the cosmic-ray probability (i.e., a cosmic ray corrupts your system)
236310
memset(filter->Fingerprints, ~0, filter->ArrayLength);
237311
free(alone);
238312
free(t2count);
239313
free(reverseH);
240314
free(t2hash);
241315
free(reverseOrder);
242316
free(startPos);
243-
return true;
317+
return false;
244318
}
245319

246320
for (uint32_t i = 0; i < block; i++) {
@@ -345,6 +419,8 @@ static inline bool binary_fuse8_populate(const uint64_t *keys, uint32_t size,
345419
// success
346420
size = stacksize;
347421
break;
422+
} else if(duplicates > 0) {
423+
size = binary_fuse_sort_and_remove_dup(keys, size);
348424
}
349425
memset(reverseOrder, 0, sizeof(uint64_t) * size);
350426
memset(t2count, 0, sizeof(uint8_t) * capacity);
@@ -439,7 +515,7 @@ static inline bool binary_fuse16_allocate(uint32_t size,
439515
}
440516
filter->SegmentLengthMask = filter->SegmentLength - 1;
441517
double sizeFactor = size <= 1 ? 0 : binary_fuse_calculate_size_factor(arity, size);
442-
uint32_t capacity = (uint32_t)(round((double)size * sizeFactor));
518+
uint32_t capacity = size <= 1 ? 0 : (uint32_t)(round((double)size * sizeFactor));
443519
uint32_t initSegmentCount =
444520
(capacity + filter->SegmentLength - 1) / filter->SegmentLength -
445521
(arity - 1);
@@ -481,7 +557,7 @@ static inline void binary_fuse16_free(binary_fuse16_t *filter) {
481557
// The caller is responsable for calling binary_fuse8_allocate(size,filter)
482558
// before. For best performance, the caller should ensure that there are not too
483559
// many duplicated keys.
484-
static inline bool binary_fuse16_populate(const uint64_t *keys, uint32_t size,
560+
static inline bool binary_fuse16_populate(uint64_t *keys, uint32_t size,
485561
binary_fuse16_t *filter) {
486562
uint64_t rng_counter = 0x726b2b9d438b9d4d;
487563
filter->Seed = binary_fuse_rng_splitmix64(&rng_counter);
@@ -514,17 +590,14 @@ static inline bool binary_fuse16_populate(const uint64_t *keys, uint32_t size,
514590
for (int loop = 0; true; ++loop) {
515591
if (loop + 1 > XOR_MAX_ITERATIONS) {
516592
// The probability of this happening is lower than the
517-
// the cosmic-ray probability (i.e., a cosmic ray corrupts your system),
518-
// but if it happens, we just fill the fingerprint with ones which
519-
// will flag all possible keys as 'possible', ensuring a correct result.
520-
memset(filter->Fingerprints, ~0, filter->ArrayLength * sizeof(uint16_t));
593+
// the cosmic-ray probability (i.e., a cosmic ray corrupts your system).
521594
free(alone);
522595
free(t2count);
523596
free(reverseH);
524597
free(t2hash);
525598
free(reverseOrder);
526599
free(startPos);
527-
return true;
600+
return false;
528601
}
529602

530603
for (uint32_t i = 0; i < block; i++) {
@@ -629,6 +702,8 @@ static inline bool binary_fuse16_populate(const uint64_t *keys, uint32_t size,
629702
// success
630703
size = stacksize;
631704
break;
705+
} else if(duplicates > 0) {
706+
size = binary_fuse_sort_and_remove_dup(keys, size);
632707
}
633708
memset(reverseOrder, 0, sizeof(uint64_t) * size);
634709
memset(t2count, 0, sizeof(uint8_t) * capacity);

src/xorfilter/xorfilter_singleheader.h

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,30 @@
77
#include <stdlib.h>
88
#include <string.h>
99

10+
#ifndef XOR_SORT_ITERATIONS
11+
#define XOR_SORT_ITERATIONS 10 // after 10 iterations, we sort and remove duplicates
12+
#endif
13+
1014
#ifndef XOR_MAX_ITERATIONS
1115
#define XOR_MAX_ITERATIONS 100 // probabillity of success should always be > 0.5 so 100 iterations is highly unlikely
1216
#endif
1317

18+
19+
static int xor_cmpfunc(const void * a, const void * b) {
20+
return ( *(const uint64_t*)a - *(const uint64_t*)b );
21+
}
22+
23+
static size_t xor_sort_and_remove_dup(uint64_t* keys, size_t length) {
24+
qsort(keys, length, sizeof(uint64_t), xor_cmpfunc);
25+
size_t j = 0;
26+
for(size_t i = 1; i < length; i++) {
27+
if(keys[i] != keys[i-1]) {
28+
keys[j] = keys[i];
29+
j++;
30+
}
31+
}
32+
return j+1;
33+
}
1434
/**
1535
* We assume that you have a large set of 64-bit integers
1636
* and you want a data structure to do membership tests using
@@ -421,10 +441,10 @@ static inline uint32_t xor_flushone_decrement_buffer(xor_setbuffer_t *buffer,
421441

422442
// Construct the filter, returns true on success, false on failure.
423443
// The algorithm fails when there is insufficient memory.
424-
// The caller is responsable for calling binary_fuse8_allocate(size,filter)
444+
// The caller is responsable for calling xor8_allocate(size,filter)
425445
// before. For best performance, the caller should ensure that there are not too
426446
// many duplicated keys.
427-
static inline bool xor8_buffered_populate(const uint64_t *keys, uint32_t size, xor8_t *filter) {
447+
static inline bool xor8_buffered_populate(uint64_t *keys, uint32_t size, xor8_t *filter) {
428448
if(size == 0) { return false; }
429449
uint64_t rng_counter = 1;
430450
filter->seed = xor_rng_splitmix64(&rng_counter);
@@ -470,12 +490,12 @@ static inline bool xor8_buffered_populate(const uint64_t *keys, uint32_t size, x
470490

471491
while (true) {
472492
iterations ++;
493+
if(iterations == XOR_SORT_ITERATIONS) {
494+
size = xor_sort_and_remove_dup(keys, size);
495+
}
473496
if(iterations > XOR_MAX_ITERATIONS) {
474497
// The probability of this happening is lower than the
475-
// the cosmic-ray probability (i.e., a cosmic ray corrupts your system),
476-
// but if it happens, we just fill the fingerprint with ones which
477-
// will flag all possible keys as 'possible', ensuring a correct result.
478-
memset(filter->fingerprints, ~0, 3 * filter->blockLength);
498+
// the cosmic-ray probability (i.e., a cosmic ray corrupts your system).
479499
xor_free_buffer(&buffer0);
480500
xor_free_buffer(&buffer1);
481501
xor_free_buffer(&buffer2);
@@ -632,10 +652,10 @@ static inline bool xor8_buffered_populate(const uint64_t *keys, uint32_t size, x
632652

633653
// Construct the filter, returns true on success, false on failure.
634654
// The algorithm fails when there is insufficient memory.
635-
// The caller is responsable for calling binary_fuse8_allocate(size,filter)
655+
// The caller is responsable for calling xor8_allocate(size,filter)
636656
// before. For best performance, the caller should ensure that there are not too
637657
// many duplicated keys.
638-
static inline bool xor8_populate(const uint64_t *keys, uint32_t size, xor8_t *filter) {
658+
static inline bool xor8_populate(uint64_t *keys, uint32_t size, xor8_t *filter) {
639659
if(size == 0) { return false; }
640660
uint64_t rng_counter = 1;
641661
filter->seed = xor_rng_splitmix64(&rng_counter);
@@ -668,12 +688,12 @@ static inline bool xor8_populate(const uint64_t *keys, uint32_t size, xor8_t *fi
668688

669689
while (true) {
670690
iterations ++;
691+
if(iterations == XOR_SORT_ITERATIONS) {
692+
size = xor_sort_and_remove_dup(keys, size);
693+
}
671694
if(iterations > XOR_MAX_ITERATIONS) {
672695
// The probability of this happening is lower than the
673-
// the cosmic-ray probability (i.e., a cosmic ray corrupts your system),
674-
// but if it happens, we just fill the fingerprint with ones which
675-
// will flag all possible keys as 'possible', ensuring a correct result.
676-
memset(filter->fingerprints, ~0, 3 * filter->blockLength);
696+
// the cosmic-ray probability (i.e., a cosmic ray corrupts your system).
677697
free(sets);
678698
free(Q);
679699
free(stack);
@@ -839,10 +859,10 @@ static inline bool xor8_populate(const uint64_t *keys, uint32_t size, xor8_t *fi
839859

840860
// Construct the filter, returns true on success, false on failure.
841861
// The algorithm fails when there is insufficient memory.
842-
// The caller is responsable for calling binary_fuse8_allocate(size,filter)
862+
// The caller is responsable for calling xor16_allocate(size,filter)
843863
// before. For best performance, the caller should ensure that there are not too
844864
// many duplicated keys.
845-
static inline bool xor16_buffered_populate(const uint64_t *keys, uint32_t size, xor16_t *filter) {
865+
static inline bool xor16_buffered_populate(uint64_t *keys, uint32_t size, xor16_t *filter) {
846866
if(size == 0) { return false; }
847867
uint64_t rng_counter = 1;
848868
filter->seed = xor_rng_splitmix64(&rng_counter);
@@ -888,12 +908,12 @@ static inline bool xor16_buffered_populate(const uint64_t *keys, uint32_t size,
888908

889909
while (true) {
890910
iterations ++;
911+
if(iterations == XOR_SORT_ITERATIONS) {
912+
size = xor_sort_and_remove_dup(keys, size);
913+
}
891914
if(iterations > XOR_MAX_ITERATIONS) {
892915
// The probability of this happening is lower than the
893-
// the cosmic-ray probability (i.e., a cosmic ray corrupts your system),
894-
// but if it happens, we just fill the fingerprint with ones which
895-
// will flag all possible keys as 'possible', ensuring a correct result.
896-
memset(filter->fingerprints, ~0, 3 * filter->blockLength * sizeof(uint16_t));
916+
// the cosmic-ray probability (i.e., a cosmic ray corrupts your system)é
897917
xor_free_buffer(&buffer0);
898918
xor_free_buffer(&buffer1);
899919
xor_free_buffer(&buffer2);
@@ -1053,10 +1073,10 @@ static inline bool xor16_buffered_populate(const uint64_t *keys, uint32_t size,
10531073

10541074
// Construct the filter, returns true on success, false on failure.
10551075
// The algorithm fails when there is insufficient memory.
1056-
// The caller is responsable for calling binary_fuse8_allocate(size,filter)
1076+
// The caller is responsable for calling xor16_allocate(size,filter)
10571077
// before. For best performance, the caller should ensure that there are not too
10581078
// many duplicated keys.
1059-
static inline bool xor16_populate(const uint64_t *keys, uint32_t size, xor16_t *filter) {
1079+
static inline bool xor16_populate(uint64_t *keys, uint32_t size, xor16_t *filter) {
10601080
if(size == 0) { return false; }
10611081
uint64_t rng_counter = 1;
10621082
filter->seed = xor_rng_splitmix64(&rng_counter);
@@ -1090,16 +1110,16 @@ static inline bool xor16_populate(const uint64_t *keys, uint32_t size, xor16_t *
10901110

10911111
while (true) {
10921112
iterations ++;
1113+
if(iterations == XOR_SORT_ITERATIONS) {
1114+
size = xor_sort_and_remove_dup(keys, size);
1115+
}
10931116
if(iterations > XOR_MAX_ITERATIONS) {
10941117
// The probability of this happening is lower than the
1095-
// the cosmic-ray probability (i.e., a cosmic ray corrupts your system),
1096-
// but if it happens, we just fill the fingerprint with ones which
1097-
// will flag all possible keys as 'possible', ensuring a correct result.
1098-
memset(filter->fingerprints, ~0, 3 * filter->blockLength * sizeof(uint16_t));
1118+
// the cosmic-ray probability (i.e., a cosmic ray corrupts your system).
10991119
free(sets);
11001120
free(Q);
11011121
free(stack);
1102-
return true;
1122+
return false;
11031123
}
11041124

11051125
memset(sets, 0, sizeof(xor_xorset_t) * arrayLength);

0 commit comments

Comments
 (0)