GPU-Accelerated Cuckoo Filter
Loading...
Searching...
No Matches
CuckooFilter.cuh
Go to the documentation of this file.
1#pragma once
2
3#include <thrust/device_vector.h>
4#include <cstdint>
5#include <cub/cub.cuh>
6#include <cuda/std/atomic>
7#include <cuda/std/cstddef>
8#include <cuda/std/cstdint>
9#include <vector>
10#include "bucket_policies.cuh"
11#include "hashutil.cuh"
12#include "helpers.cuh"
13
14namespace cuckoogpu {
15
19enum class EvictionPolicy {
20 BFS,
21 DFS
22};
23
39template <
40 typename T,
41 size_t bitsPerTag_,
42 size_t maxEvictions_ = 500,
43 size_t blockSize_ = 256,
44 size_t bucketSize_ = 16,
45 template <typename, typename, size_t, size_t> class AltBucketPolicy_ = XorAltBucketPolicy,
46 EvictionPolicy evictionPolicy_ = EvictionPolicy::BFS,
47 typename WordType_ = uint64_t>
48struct Config {
49 using KeyType = T;
50 static constexpr size_t bitsPerTag = bitsPerTag_;
51 static constexpr size_t maxEvictions = maxEvictions_;
52 static constexpr size_t blockSize = blockSize_;
53 static constexpr size_t bucketSize = bucketSize_;
54 static constexpr EvictionPolicy evictionPolicy = evictionPolicy_;
55
56 using TagType = typename std::conditional<
57 bitsPerTag <= 8,
58 uint8_t,
59 typename std::conditional<bitsPerTag <= 16, uint16_t, uint32_t>::type>::type;
60
61 using WordType = WordType_;
62 static_assert(
63 std::is_same_v<WordType, uint32_t> || std::is_same_v<WordType, uint64_t>,
64 "WordType must be uint32_t or uint64_t"
65 );
66 static_assert(sizeof(TagType) <= sizeof(WordType), "TagType must fit within WordType");
67
68 using AltBucketPolicy = AltBucketPolicy_<KeyType, TagType, bitsPerTag, bucketSize_>;
69};
70
71template <typename Config>
72class Filter;
73
74namespace detail {
75
79template <typename Config>
80__global__ void insertKernel(
81 const typename Config::KeyType* keys,
82 bool* output,
83 size_t n,
84 Filter<Config>* filter,
85 uint32_t* evictionAttempts
86);
87
91template <typename Config>
92__global__ void insertKernelSorted(
93 const typename Filter<Config>::PackedTagType* packedTags,
94 bool* output,
95 size_t n,
96 Filter<Config>* filter,
97 uint32_t* evictionAttempts
98);
99
103template <typename Config>
104__global__ void computePackedTagsKernel(
105 const typename Config::KeyType* keys,
106 typename Filter<Config>::PackedTagType* packedTags,
107 size_t n,
108 size_t numBuckets
109);
110
114template <typename Config>
115__global__ void containsKernel(
116 const typename Config::KeyType* keys,
117 bool* output,
118 size_t n,
119 Filter<Config>* filter
120);
121
125template <typename Config>
126__global__ void
127deleteKernel(const typename Config::KeyType* keys, bool* output, size_t n, Filter<Config>* filter);
128
129} // namespace detail
130
140template <typename Config>
141struct Filter {
142 using T = typename Config::KeyType;
143 static constexpr size_t bitsPerTag = Config::bitsPerTag;
144
145 using TagType = typename Config::TagType;
147
148 static constexpr size_t tagEntryBytes = sizeof(TagType);
149 static constexpr size_t bucketSize = Config::bucketSize;
150
151 static constexpr size_t maxEvictions = Config::maxEvictions;
152 static constexpr size_t blockSize = Config::blockSize;
153 static_assert(
154 bitsPerTag == 8 || bitsPerTag == 16 || bitsPerTag == 32,
155 "The tag must be 8, 16 or 32 bits"
156 );
157
158 static_assert(detail::powerOfTwo(bucketSize), "Bucket size must be a power of 2");
159
160 using PackedTagType = typename std::conditional<bitsPerTag <= 8, uint32_t, uint64_t>::type;
161
168 struct PackedTag {
170
171 // Lower bits = fingerprint
172 // Upper bits = bucket index
173 static constexpr size_t fpBits = bitsPerTag;
174 static constexpr size_t totalBits = sizeof(PackedTagType) * 8;
175 static constexpr size_t bucketIdxBits = totalBits - fpBits;
176
177 static_assert(fpBits < totalBits, "fpBits must leave at least some bits for bucketIdx");
178
179 static constexpr PackedTagType fpMask = PackedTagType((1ULL << fpBits) - 1ULL);
180
181 static constexpr PackedTagType bucketIdxMask =
182 PackedTagType(((1ULL << bucketIdxBits) - 1ULL) << fpBits);
183
184 __host__ __device__ PackedTag() : value(0) {
185 }
186
187 __host__ __device__ PackedTag(TagType fp, uint64_t bucketIdx) : value(0) {
188 setFingerprint(fp);
189 setBucketIdx(bucketIdx);
190 }
191
192 __host__ __device__ TagType getFingerprint() const {
193 return static_cast<TagType>(value & fpMask);
194 }
195
196 __host__ __device__ uint64_t getBucketIndex() const {
197 return uint64_t((value & bucketIdxMask) >> fpBits);
198 }
199
200 __host__ __device__ void setFingerprint(TagType fp) {
201 value = (value & ~fpMask) | (static_cast<PackedTagType>(fp) & fpMask);
202 }
203
204 __host__ __device__ void setBucketIdx(size_t bucketIdx) {
205 PackedTagType v = static_cast<PackedTagType>(bucketIdx) << fpBits;
206 value = (value & ~bucketIdxMask) | v;
207 }
208 };
209
210 static constexpr TagType EMPTY = 0;
211 static constexpr size_t fpMask = (1ULL << bitsPerTag) - 1;
212
225 struct Bucket {
226 static_assert(detail::powerOfTwo(bitsPerTag), "bitsPerTag must be a power of 2");
227
228 using WordType = typename Config::WordType;
229
230 static constexpr size_t tagsPerWord = sizeof(WordType) / sizeof(TagType);
231 static_assert(tagsPerWord >= 1, "TagType must fit within WordType");
232 static_assert(bucketSize % tagsPerWord == 0, "bucketSize must be divisible by tagsPerWord");
233 static_assert(detail::powerOfTwo(tagsPerWord), "tagsPerWord must be a power of 2");
234
235 static constexpr size_t wordCount = bucketSize / tagsPerWord;
236 static_assert(detail::powerOfTwo(wordCount), "wordCount must be a power of 2");
237
238 cuda::std::atomic<WordType> packedTags[wordCount];
239
240 __host__ __device__ __forceinline__ TagType
241 extractTag(WordType packed, size_t tagIdx) const {
242 return static_cast<TagType>((packed >> (tagIdx * bitsPerTag)) & fpMask);
243 }
244
245 __host__ __device__ __forceinline__ WordType
246 replaceTag(WordType packed, size_t tagIdx, TagType newTag) const {
247 size_t shift = tagIdx * bitsPerTag;
248 WordType cleared = packed & ~(static_cast<WordType>(fpMask) << shift);
249 return cleared | (static_cast<WordType>(newTag) << shift);
250 }
251
255 template <size_t N>
256 __device__ __forceinline__ static bool
257 checkWords(const WordType (&loaded)[N], WordType replicatedTag) {
258 _Pragma("unroll")
259 for (size_t j = 0; j < N; ++j) {
260 if (detail::hasZero<TagType, WordType>(loaded[j] ^ replicatedTag)) {
261 return true;
262 }
263 }
264 return false;
265 }
266
270 template <size_t N>
271 __device__ __forceinline__ void load128Bit(size_t startIdx, WordType (&out)[N]) const {
272 static_assert(N == 2 || N == 4, "128-bit loads support 2 or 4 words");
273 if constexpr (sizeof(WordType) == 4) {
274 auto vec = __ldg(reinterpret_cast<const uint4*>(&packedTags[startIdx]));
275 out[0] = vec.x;
276 out[1] = vec.y;
277 out[2] = vec.z;
278 out[3] = vec.w;
279 } else {
280 auto vec = __ldg(reinterpret_cast<const ulonglong2*>(&packedTags[startIdx]));
281 out[0] = vec.x;
282 out[1] = vec.y;
283 }
284 }
285
291 __device__ bool contains(TagType tag) const {
292 const WordType replicatedTag = detail::replicateTag<TagType, WordType>(tag);
293
294 // Scalar path
295 if constexpr (wordCount == 1) {
296 const auto packed = reinterpret_cast<const WordType&>(packedTags[0]);
297 return detail::hasZero<TagType, WordType>(packed ^ replicatedTag);
298 }
299
300 const uint32_t startSlot = tag & (bucketSize - 1);
301 const size_t startWordIdx = startSlot / tagsPerWord;
302
303#if __CUDA_ARCH__ >= 1000 && !defined(CUCKOO_FILTER_DISABLE_256BIT_LOADS)
304 // 256-bit load path
305 constexpr size_t wordsPerLoad256 = (sizeof(WordType) == 4) ? 8 : 4;
306 if constexpr (wordCount >= wordsPerLoad256) {
307 constexpr size_t alignMask = wordsPerLoad256 - 1;
308 const size_t startAlignedIdx = startWordIdx & ~alignMask;
309
310 WordType loaded[wordsPerLoad256];
311 for (size_t i = 0; i < wordCount / wordsPerLoad256; i++) {
312 const size_t idx = (startAlignedIdx + i * wordsPerLoad256) & (wordCount - 1);
314 reinterpret_cast<const WordType*>(&packedTags[idx]), loaded
315 );
316 if (checkWords(loaded, replicatedTag)) {
317 return true;
318 }
319 }
320 return false;
321 }
322#endif
323 // 128-bit load path
324 constexpr size_t wordsPerLoad128 = (sizeof(WordType) == 4) ? 4 : 2;
325 if constexpr (wordCount >= wordsPerLoad128) {
326 constexpr size_t alignMask = wordsPerLoad128 - 1;
327 const size_t startAlignedIdx = startWordIdx & ~alignMask;
328
329 WordType loaded[wordsPerLoad128];
330 for (size_t i = 0; i < wordCount / wordsPerLoad128; i++) {
331 const size_t idx = (startAlignedIdx + i * wordsPerLoad128) & (wordCount - 1);
332 load128Bit(idx, loaded);
333 if (checkWords(loaded, replicatedTag)) {
334 return true;
335 }
336 }
337 return false;
338 }
339
340 return false;
341 }
342 };
343
344 size_t numBuckets;
346 cuda::std::atomic<size_t>*
348
349#ifdef CUCKOO_FILTER_COUNT_DELETE_CAS
350 cuda::std::atomic<size_t>*
352 cuda::std::atomic<size_t>*
354#endif
355
356#ifdef CUCKOO_FILTER_COUNT_EVICTIONS
357 cuda::std::atomic<size_t>* d_numEvictions{};
358#endif
359
360 size_t h_numOccupied = 0;
361
362 template <typename H>
363 static __host__ __device__ uint64_t hash64(const H& key) {
364 return AltBucketPolicy::hash64(key);
365 }
366
367 static __host__ __device__ cuda::std::tuple<size_t, size_t, TagType, TagType>
369 return AltBucketPolicy::getCandidateBucketsAndFPs(key, numBuckets);
370 }
371
375 static __host__ __device__ size_t
376 getAlternateBucket(size_t bucket, TagType fp, size_t numBuckets) {
377 return AltBucketPolicy::getAlternateBucket(bucket, fp, numBuckets);
378 }
379
384 static __host__ __device__ cuda::std::tuple<size_t, TagType>
385 getAlternateBucketWithNewFp(size_t bucket, TagType fp, size_t numBuckets) {
386 if constexpr (AltBucketPolicy::usesChoiceBit) {
387 return AltBucketPolicy::getAlternateBucketWithNewFp(bucket, fp, numBuckets);
388 } else {
389 return {AltBucketPolicy::getAlternateBucket(bucket, fp, numBuckets), fp};
390 }
391 }
392
397 static size_t calculateNumBuckets(size_t capacity) {
398 return AltBucketPolicy::calculateNumBuckets(capacity);
399 }
400
401 Filter(const Filter&) = delete;
402 Filter& operator=(const Filter&) = delete;
403
412 CUCKOO_CUDA_CALL(cudaMalloc(&d_buckets, numBuckets * sizeof(Bucket)));
413 CUCKOO_CUDA_CALL(cudaMalloc(&d_numOccupied, sizeof(cuda::std::atomic<size_t>)));
414#ifdef CUCKOO_FILTER_COUNT_DELETE_CAS
415 CUCKOO_CUDA_CALL(cudaMalloc(&d_deleteCasAttempts, sizeof(cuda::std::atomic<size_t>)));
416 CUCKOO_CUDA_CALL(cudaMalloc(&d_deleteCasFailures, sizeof(cuda::std::atomic<size_t>)));
417#endif
418#ifdef CUCKOO_FILTER_COUNT_EVICTIONS
419 CUCKOO_CUDA_CALL(cudaMalloc(&d_numEvictions, sizeof(cuda::std::atomic<size_t>)));
420#endif
421
422 clear();
423 }
424
431 if (d_buckets) {
432 CUCKOO_CUDA_CALL(cudaFree(d_buckets));
433 }
434 if (d_numOccupied) {
436 }
437#ifdef CUCKOO_FILTER_COUNT_DELETE_CAS
440 }
443 }
444#endif
445#ifdef CUCKOO_FILTER_COUNT_EVICTIONS
446 if (d_numEvictions) {
447 CUCKOO_CUDA_CALL(cudaFree(d_numEvictions));
448 }
449#endif
450 }
451
463 const T* d_keys,
464 const size_t n,
465 bool* d_output = nullptr,
466 cudaStream_t stream = {}
467 ) {
468 size_t numBlocks = SDIV(n, blockSize);
470 <<<numBlocks, blockSize, 0, stream>>>(d_keys, d_output, n, this, nullptr);
471
472 CUCKOO_CUDA_CALL(cudaStreamSynchronize(stream));
473
474 return occupiedSlots();
475 }
476
477#ifdef CUCKOO_FILTER_COUNT_EVICTIONS
489 size_t insertManyWithEvictionCounts(
490 const T* d_keys,
491 const size_t n,
492 uint32_t* d_evictionAttempts,
493 bool* d_output = nullptr,
494 cudaStream_t stream = {}
495 ) {
496 size_t numBlocks = SDIV(n, blockSize);
498 <<<numBlocks, blockSize, 0, stream>>>(d_keys, d_output, n, this, d_evictionAttempts);
499
500 CUCKOO_CUDA_CALL(cudaStreamSynchronize(stream));
501
502 return occupiedSlots();
503 }
504#endif
505
518 const T* d_keys,
519 const size_t n,
520 bool* d_output = nullptr,
521 cudaStream_t stream = {}
522 ) {
523 PackedTagType* d_packedTags;
524
525 CUCKOO_CUDA_CALL(cudaMallocAsync(&d_packedTags, n * sizeof(PackedTagType), stream));
526
527 size_t numBlocks = SDIV(n, blockSize);
528
530 <<<numBlocks, blockSize, 0, stream>>>(d_keys, d_packedTags, n, numBuckets);
531
532 void* d_tempStorage = nullptr;
533 size_t tempStorageBytes = 0;
534
535 cub::DeviceRadixSort::SortKeys(
536 d_tempStorage,
537 tempStorageBytes,
538 d_packedTags,
539 d_packedTags,
540 n,
541 0,
542 sizeof(PackedTagType) * 8,
543 stream
544 );
545
546 CUCKOO_CUDA_CALL(cudaMallocAsync(&d_tempStorage, tempStorageBytes, stream));
547
548 cub::DeviceRadixSort::SortKeys(
549 d_tempStorage,
550 tempStorageBytes,
551 d_packedTags,
552 d_packedTags,
553 n,
554 0,
555 sizeof(PackedTagType) * 8,
556 stream
557 );
558
559 CUCKOO_CUDA_CALL(cudaFreeAsync(d_tempStorage, stream));
560
562 <<<numBlocks, blockSize, 0, stream>>>(d_packedTags, d_output, n, this, nullptr);
563
564 CUCKOO_CUDA_CALL(cudaFreeAsync(d_packedTags, stream));
565 CUCKOO_CUDA_CALL(cudaStreamSynchronize(stream));
566
567 return occupiedSlots();
568 }
569
570#ifdef CUCKOO_FILTER_COUNT_EVICTIONS
582 size_t insertManySortedWithEvictionCounts(
583 const T* d_keys,
584 const size_t n,
585 uint32_t* d_evictionAttempts,
586 bool* d_output = nullptr,
587 cudaStream_t stream = {}
588 ) {
589 PackedTagType* d_packedTags;
590
591 CUCKOO_CUDA_CALL(cudaMallocAsync(&d_packedTags, n * sizeof(PackedTagType), stream));
592
593 size_t numBlocks = SDIV(n, blockSize);
594
596 <<<numBlocks, blockSize, 0, stream>>>(d_keys, d_packedTags, n, numBuckets);
597
598 void* d_tempStorage = nullptr;
599 size_t tempStorageBytes = 0;
600
601 cub::DeviceRadixSort::SortKeys(
602 d_tempStorage,
603 tempStorageBytes,
604 d_packedTags,
605 d_packedTags,
606 n,
607 0,
608 sizeof(PackedTagType) * 8,
609 stream
610 );
611
612 CUCKOO_CUDA_CALL(cudaMallocAsync(&d_tempStorage, tempStorageBytes, stream));
613
614 cub::DeviceRadixSort::SortKeys(
615 d_tempStorage,
616 tempStorageBytes,
617 d_packedTags,
618 d_packedTags,
619 n,
620 0,
621 sizeof(PackedTagType) * 8,
622 stream
623 );
624
625 CUCKOO_CUDA_CALL(cudaFreeAsync(d_tempStorage, stream));
626
627 detail::insertKernelSorted<Config><<<numBlocks, blockSize, 0, stream>>>(
628 d_packedTags, d_output, n, this, d_evictionAttempts
629 );
630
631 CUCKOO_CUDA_CALL(cudaFreeAsync(d_packedTags, stream));
632 CUCKOO_CUDA_CALL(cudaStreamSynchronize(stream));
633
634 return occupiedSlots();
635 }
636#endif
637
646 void containsMany(const T* d_keys, const size_t n, bool* d_output, cudaStream_t stream = {}) {
647 size_t numBlocks = SDIV(n, blockSize);
649 <<<numBlocks, blockSize, 0, stream>>>(d_keys, d_output, n, this);
650
651 CUCKOO_CUDA_CALL(cudaStreamSynchronize(stream));
652 }
653
666 const T* d_keys,
667 const size_t n,
668 bool* d_output = nullptr,
669 cudaStream_t stream = {}
670 ) {
671 size_t numBlocks = SDIV(n, blockSize);
673 <<<numBlocks, blockSize, 0, stream>>>(d_keys, d_output, n, this);
674
675 CUCKOO_CUDA_CALL(cudaStreamSynchronize(stream));
676
677 return occupiedSlots();
678 }
679
688 const thrust::device_vector<T>& d_keys,
689 thrust::device_vector<bool>& d_output,
690 cudaStream_t stream = {}
691 ) {
692 if (d_output.size() != d_keys.size()) {
693 d_output.resize(d_keys.size());
694 }
695 return insertMany(
696 thrust::raw_pointer_cast(d_keys.data()),
697 d_keys.size(),
698 thrust::raw_pointer_cast(d_output.data()),
699 stream
700 );
701 }
702
711 const thrust::device_vector<T>& d_keys,
712 thrust::device_vector<uint8_t>& d_output,
713 cudaStream_t stream = {}
714 ) {
715 if (d_output.size() != d_keys.size()) {
716 d_output.resize(d_keys.size());
717 }
718 return insertMany(
719 thrust::raw_pointer_cast(d_keys.data()),
720 d_keys.size(),
721 reinterpret_cast<bool*>(thrust::raw_pointer_cast(d_output.data())),
722 stream
723 );
724 }
725
732 size_t insertMany(const thrust::device_vector<T>& d_keys, cudaStream_t stream = {}) {
733 return insertMany(thrust::raw_pointer_cast(d_keys.data()), d_keys.size(), nullptr, stream);
734 }
735
736#ifdef CUCKOO_FILTER_COUNT_EVICTIONS
745 size_t insertManyWithEvictionCounts(
746 const thrust::device_vector<T>& d_keys,
747 thrust::device_vector<uint32_t>& d_evictionAttempts,
748 thrust::device_vector<uint8_t>* d_output = nullptr,
749 cudaStream_t stream = {}
750 ) {
751 if (d_evictionAttempts.size() != d_keys.size()) {
752 d_evictionAttempts.resize(d_keys.size());
753 }
754
755 bool* d_outputPtr = nullptr;
756 if (d_output != nullptr) {
757 if (d_output->size() != d_keys.size()) {
758 d_output->resize(d_keys.size());
759 }
760 d_outputPtr = reinterpret_cast<bool*>(thrust::raw_pointer_cast(d_output->data()));
761 }
762
763 return insertManyWithEvictionCounts(
764 thrust::raw_pointer_cast(d_keys.data()),
765 d_keys.size(),
766 thrust::raw_pointer_cast(d_evictionAttempts.data()),
767 d_outputPtr,
768 stream
769 );
770 }
771#endif
772
781 const thrust::device_vector<T>& d_keys,
782 thrust::device_vector<bool>& d_output,
783 cudaStream_t stream = {}
784 ) {
785 if (d_output.size() != d_keys.size()) {
786 d_output.resize(d_keys.size());
787 }
788 return insertManySorted(
789 thrust::raw_pointer_cast(d_keys.data()),
790 d_keys.size(),
791 thrust::raw_pointer_cast(d_output.data()),
792 stream
793 );
794 }
795
804 const thrust::device_vector<T>& d_keys,
805 thrust::device_vector<uint8_t>& d_output,
806 cudaStream_t stream = {}
807 ) {
808 if (d_output.size() != d_keys.size()) {
809 d_output.resize(d_keys.size());
810 }
811 return insertManySorted(
812 thrust::raw_pointer_cast(d_keys.data()),
813 d_keys.size(),
814 reinterpret_cast<bool*>(thrust::raw_pointer_cast(d_output.data())),
815 stream
816 );
817 }
818
826 size_t insertManySorted(const thrust::device_vector<T>& d_keys, cudaStream_t stream = {}) {
827 return insertManySorted(
828 thrust::raw_pointer_cast(d_keys.data()), d_keys.size(), nullptr, stream
829 );
830 }
831
832#ifdef CUCKOO_FILTER_COUNT_EVICTIONS
842 size_t insertManySortedWithEvictionCounts(
843 const thrust::device_vector<T>& d_keys,
844 thrust::device_vector<uint32_t>& d_evictionAttempts,
845 thrust::device_vector<uint8_t>* d_output = nullptr,
846 cudaStream_t stream = {}
847 ) {
848 if (d_evictionAttempts.size() != d_keys.size()) {
849 d_evictionAttempts.resize(d_keys.size());
850 }
851
852 bool* d_outputPtr = nullptr;
853 if (d_output != nullptr) {
854 if (d_output->size() != d_keys.size()) {
855 d_output->resize(d_keys.size());
856 }
857 d_outputPtr = reinterpret_cast<bool*>(thrust::raw_pointer_cast(d_output->data()));
858 }
859
860 return insertManySortedWithEvictionCounts(
861 thrust::raw_pointer_cast(d_keys.data()),
862 d_keys.size(),
863 thrust::raw_pointer_cast(d_evictionAttempts.data()),
864 d_outputPtr,
865 stream
866 );
867 }
868#endif
869
877 const thrust::device_vector<T>& d_keys,
878 thrust::device_vector<bool>& d_output,
879 cudaStream_t stream = {}
880 ) {
881 if (d_output.size() != d_keys.size()) {
882 d_output.resize(d_keys.size());
883 }
885 thrust::raw_pointer_cast(d_keys.data()),
886 d_keys.size(),
887 thrust::raw_pointer_cast(d_output.data()),
888 stream
889 );
890 }
891
899 const thrust::device_vector<T>& d_keys,
900 thrust::device_vector<uint8_t>& d_output,
901 cudaStream_t stream = {}
902 ) {
903 if (d_output.size() != d_keys.size()) {
904 d_output.resize(d_keys.size());
905 }
907 thrust::raw_pointer_cast(d_keys.data()),
908 d_keys.size(),
909 reinterpret_cast<bool*>(thrust::raw_pointer_cast(d_output.data())),
910 stream
911 );
912 }
913
922 const thrust::device_vector<T>& d_keys,
923 thrust::device_vector<bool>& d_output,
924 cudaStream_t stream = {}
925 ) {
926 if (d_output.size() != d_keys.size()) {
927 d_output.resize(d_keys.size());
928 }
929 return deleteMany(
930 thrust::raw_pointer_cast(d_keys.data()),
931 d_keys.size(),
932 thrust::raw_pointer_cast(d_output.data()),
933 stream
934 );
935 }
936
945 const thrust::device_vector<T>& d_keys,
946 thrust::device_vector<uint8_t>& d_output,
947 cudaStream_t stream = {}
948 ) {
949 if (d_output.size() != d_keys.size()) {
950 d_output.resize(d_keys.size());
951 }
952 return deleteMany(
953 thrust::raw_pointer_cast(d_keys.data()),
954 d_keys.size(),
955 reinterpret_cast<bool*>(thrust::raw_pointer_cast(d_output.data())),
956 stream
957 );
958 }
959
966 size_t deleteMany(const thrust::device_vector<T>& d_keys, cudaStream_t stream = {}) {
967 return deleteMany(thrust::raw_pointer_cast(d_keys.data()), d_keys.size(), nullptr, stream);
968 }
969
973 void clear() {
974 CUCKOO_CUDA_CALL(cudaMemset(d_buckets, 0, numBuckets * sizeof(Bucket)));
975 CUCKOO_CUDA_CALL(cudaMemset(d_numOccupied, 0, sizeof(cuda::std::atomic<size_t>)));
976#ifdef CUCKOO_FILTER_COUNT_DELETE_CAS
977 CUCKOO_CUDA_CALL(cudaMemset(d_deleteCasAttempts, 0, sizeof(cuda::std::atomic<size_t>)));
978 CUCKOO_CUDA_CALL(cudaMemset(d_deleteCasFailures, 0, sizeof(cuda::std::atomic<size_t>)));
979#endif
980#ifdef CUCKOO_FILTER_COUNT_EVICTIONS
981 CUCKOO_CUDA_CALL(cudaMemset(d_numEvictions, 0, sizeof(cuda::std::atomic<size_t>)));
982#endif
983 h_numOccupied = 0;
984 }
985
990 [[nodiscard]] float loadFactor() {
991 return static_cast<float>(occupiedSlots()) / (numBuckets * bucketSize);
992 }
993
1001 size_t occupiedSlots() {
1003 cudaMemcpy(&h_numOccupied, d_numOccupied, sizeof(size_t), cudaMemcpyDeviceToHost)
1004 );
1005 return h_numOccupied;
1006 }
1007
1008#ifdef CUCKOO_FILTER_COUNT_EVICTIONS
1016 size_t evictionCount() {
1017 size_t count;
1019 cudaMemcpy(&count, d_numEvictions, sizeof(size_t), cudaMemcpyDeviceToHost)
1020 );
1021 return count;
1022 }
1023
1027 void resetEvictionCount() {
1028 CUCKOO_CUDA_CALL(cudaMemset(d_numEvictions, 0, sizeof(cuda::std::atomic<size_t>)));
1029 }
1030#endif
1031
1032#ifdef CUCKOO_FILTER_COUNT_DELETE_CAS
1041 size_t count;
1043 cudaMemcpy(&count, d_deleteCasAttempts, sizeof(size_t), cudaMemcpyDeviceToHost)
1044 );
1045 return count;
1046 }
1047
1056 size_t count;
1058 cudaMemcpy(&count, d_deleteCasFailures, sizeof(size_t), cudaMemcpyDeviceToHost)
1059 );
1060 return count;
1061 }
1062
1067 CUCKOO_CUDA_CALL(cudaMemset(d_deleteCasAttempts, 0, sizeof(cuda::std::atomic<size_t>)));
1068 CUCKOO_CUDA_CALL(cudaMemset(d_deleteCasFailures, 0, sizeof(cuda::std::atomic<size_t>)));
1069 }
1070#endif
1071
1076 size_t capacity() {
1077 return numBuckets * bucketSize;
1078 }
1079
1084 [[nodiscard]] size_t getNumBuckets() const {
1085 return numBuckets;
1086 }
1087
1092 [[nodiscard]] size_t sizeInBytes() const {
1093 return numBuckets * sizeof(Bucket);
1094 }
1095
1104 std::vector<Bucket> h_buckets(numBuckets);
1105
1106 CUCKOO_CUDA_CALL(cudaMemcpy(
1107 h_buckets.data(), d_buckets, numBuckets * sizeof(Bucket), cudaMemcpyDeviceToHost
1108 ));
1109
1110 size_t occupiedCount = 0;
1111
1112 for (size_t bucketIdx = 0; bucketIdx < numBuckets; ++bucketIdx) {
1113 const Bucket& bucket = h_buckets[bucketIdx];
1114
1115 for (size_t atomicIdx = 0; atomicIdx < Bucket::wordCount; ++atomicIdx) {
1116 uint64_t packed = reinterpret_cast<const uint64_t&>(bucket.packedTags[atomicIdx]);
1117
1118 for (size_t tagIdx = 0; tagIdx < Bucket::tagsPerWord; ++tagIdx) {
1119 auto tag = bucket.extractTag(packed, tagIdx);
1120
1121 if (tag != EMPTY) {
1122 occupiedCount++;
1123 }
1124 }
1125 }
1126 }
1127
1128 return occupiedCount;
1129 }
1130
1149 __device__ bool tryRemoveAtBucket(size_t bucketIdx, TagType tag) {
1150 Bucket& bucket = d_buckets[bucketIdx];
1151
1152 const uint32_t startSlot = tag & (bucketSize - 1);
1153 const size_t startWord = startSlot / Bucket::tagsPerWord;
1154
1155 using WordType = typename Bucket::WordType;
1156
1157 for (size_t i = 0; i < Bucket::wordCount; ++i) {
1158 const size_t currIdx = (startWord + i) & (Bucket::wordCount - 1);
1159
1160 while (true) {
1161 auto expected = bucket.packedTags[currIdx].load(cuda::memory_order_relaxed);
1162
1163 WordType matchMask = detail::getZeroMask<TagType, WordType>(
1165 );
1166
1167 if (matchMask == 0) {
1168 // No matching tags in this word
1169 break;
1170 }
1171
1172 // Find position of first matching tag
1173 int bitPos;
1174 if constexpr (sizeof(WordType) == 4) {
1175 bitPos = __ffs(static_cast<int>(matchMask)) - 1;
1176 } else {
1177 bitPos = __ffsll(static_cast<long long>(matchMask)) - 1;
1178 }
1179 size_t tagIdx = bitPos / bitsPerTag;
1180
1181 auto desired = bucket.replaceTag(expected, tagIdx, EMPTY);
1182
1183#ifdef CUCKOO_FILTER_COUNT_DELETE_CAS
1184 d_deleteCasAttempts->fetch_add(1, cuda::memory_order_relaxed);
1185#endif
1186 bool casSuccess = bucket.packedTags[currIdx].compare_exchange_weak(
1187 expected, desired, cuda::memory_order_relaxed, cuda::memory_order_relaxed
1188 );
1189#ifdef CUCKOO_FILTER_COUNT_DELETE_CAS
1190 if (!casSuccess) {
1191 d_deleteCasFailures->fetch_add(1, cuda::memory_order_relaxed);
1192 }
1193#endif
1194 if (casSuccess) {
1195 return true;
1196 }
1197 // CAS failed, retry with updated expected value
1198 }
1199 }
1200
1201 return false;
1202 }
1203
1213 __device__ bool tryInsertAtBucket(size_t bucketIdx, TagType tag) {
1214 Bucket& bucket = d_buckets[bucketIdx];
1215 const uint32_t startIdx = tag & (bucketSize - 1);
1216 const size_t startWord = startIdx / Bucket::tagsPerWord;
1217
1218 using WordType = typename Bucket::WordType;
1219
1220 for (size_t i = 0; i < Bucket::wordCount; ++i) {
1221 const size_t currWord = (startWord + i) & (Bucket::wordCount - 1);
1222 auto expected = bucket.packedTags[currWord].load(cuda::memory_order_relaxed);
1223
1224 while (true) {
1225 WordType zeroMask = detail::getZeroMask<TagType, WordType>(expected);
1226
1227 if (zeroMask == 0) {
1228 // No empty slots in this word, move to next
1229 break;
1230 }
1231
1232 // Find position of first empty slot (returns 1-indexed bit position)
1233 int bitPos;
1234 if constexpr (sizeof(WordType) == 4) {
1235 bitPos = __ffs(static_cast<int>(zeroMask)) - 1;
1236 } else {
1237 bitPos = __ffsll(static_cast<long long>(zeroMask)) - 1;
1238 }
1239 size_t j = bitPos / bitsPerTag;
1240
1241 auto desired = bucket.replaceTag(expected, j, tag);
1242
1243 if (bucket.packedTags[currWord].compare_exchange_strong(
1244 expected, desired, cuda::memory_order_relaxed, cuda::memory_order_relaxed
1245 )) {
1246 return true;
1247 }
1248 }
1249 }
1250 return false;
1251 }
1252
1265 __device__ bool
1266 insertWithEvictionDFS(TagType fp, size_t startBucket, uint32_t* evictionAttempts = nullptr) {
1267 TagType currentFp = fp;
1268 size_t currentBucket = startBucket;
1269
1270 for (size_t evictions = 0; evictions < maxEvictions; ++evictions) {
1271 auto evictSlot = (currentFp + (evictions + 1) * 0x9E3779B1UL) & (bucketSize - 1);
1272
1273 size_t evictWord = evictSlot / Bucket::tagsPerWord;
1274 size_t evictTagIdx = evictSlot & (Bucket::tagsPerWord - 1);
1275
1276 Bucket& bucket = d_buckets[currentBucket];
1277 auto expected = bucket.packedTags[evictWord].load(cuda::memory_order_relaxed);
1278 typename Bucket::WordType desired;
1279 TagType evictedFp;
1280
1281 do {
1282 evictedFp = bucket.extractTag(expected, evictTagIdx);
1283 desired = bucket.replaceTag(expected, evictTagIdx, currentFp);
1284 } while (!bucket.packedTags[evictWord].compare_exchange_strong(
1285 expected, desired, cuda::memory_order_relaxed, cuda::memory_order_relaxed
1286 ));
1287
1288#ifdef CUCKOO_FILTER_COUNT_EVICTIONS
1289 d_numEvictions->fetch_add(1, cuda::memory_order_relaxed);
1290 if (evictionAttempts != nullptr) {
1291 (*evictionAttempts)++;
1292 }
1293#endif
1294
1295 currentFp = evictedFp;
1296 auto [altBucket, newFp] =
1297 getAlternateBucketWithNewFp(currentBucket, evictedFp, numBuckets);
1298 currentBucket = altBucket;
1299 currentFp = newFp;
1300
1301 if (tryInsertAtBucket(currentBucket, currentFp)) {
1302 return true;
1303 }
1304 }
1305 return false;
1306 }
1307
1322 __device__ bool
1323 insertWithEvictionBFS(TagType fp, size_t startBucket, uint32_t* evictionAttempts = nullptr) {
1324 constexpr size_t numCandidates = std::max(1UL, bucketSize / 2);
1325
1326 TagType currentFp = fp;
1327 size_t currentBucket = startBucket;
1328
1329 size_t evictions = 0;
1330 while (evictions < maxEvictions) {
1331 Bucket& bucket = d_buckets[currentBucket];
1332 size_t restartWord = 0;
1333 size_t restartTagIdx = 0;
1334
1335 for (size_t i = 0; i < numCandidates; ++i) {
1336 size_t evictSlot = (currentFp + i * 0x9E3779B1UL + (evictions + 1) * 0x85EBCA77) &
1337 (bucketSize - 1);
1338 size_t evictWord = evictSlot / Bucket::tagsPerWord;
1339 size_t evictTagIdx = evictSlot & (Bucket::tagsPerWord - 1);
1340 restartWord = evictWord;
1341 restartTagIdx = evictTagIdx;
1342
1343 auto packed = bucket.packedTags[evictWord].load(cuda::memory_order_relaxed);
1344 TagType candidateFp = bucket.extractTag(packed, evictTagIdx);
1345
1346 if (candidateFp == EMPTY) {
1347 if (tryInsertAtBucket(currentBucket, currentFp)) {
1348 return true;
1349 }
1350 continue;
1351 }
1352
1353 auto [altBucket, altFp] =
1354 getAlternateBucketWithNewFp(currentBucket, candidateFp, numBuckets);
1355 if (tryInsertAtBucket(altBucket, altFp)) {
1356 // Successfully inserted the evicted tag at its alternate location
1357 // Now atomically swap in our tag at the original location
1358 auto expected = bucket.packedTags[evictWord].load(cuda::memory_order_relaxed);
1359
1360 // Verify the tag is still there and try to replace it
1361 if (bucket.extractTag(expected, evictTagIdx) == candidateFp) {
1362 auto desired = bucket.replaceTag(expected, evictTagIdx, currentFp);
1363
1364 if (bucket.packedTags[evictWord].compare_exchange_strong(
1365 expected,
1366 desired,
1367 cuda::memory_order_relaxed,
1368 cuda::memory_order_relaxed
1369 )) {
1370#ifdef CUCKOO_FILTER_COUNT_EVICTIONS
1371 d_numEvictions->fetch_add(1, cuda::memory_order_relaxed);
1372 if (evictionAttempts != nullptr) {
1373 (*evictionAttempts)++;
1374 }
1375#endif
1376 return true;
1377 }
1378 }
1379
1380 // Failed to swap, clean up the tag we inserted to avoid duplicates
1381 tryRemoveAtBucket(altBucket, candidateFp);
1382 }
1383 }
1384
1385 // Evict the last scanned candidate and continue from its alternate location.
1386 auto expected = bucket.packedTags[restartWord].load(cuda::memory_order_relaxed);
1387 typename Bucket::WordType desired;
1388 TagType evictedFp;
1389
1390 do {
1391 evictedFp = bucket.extractTag(expected, restartTagIdx);
1392 desired = bucket.replaceTag(expected, restartTagIdx, currentFp);
1393 } while (!bucket.packedTags[restartWord].compare_exchange_strong(
1394 expected, desired, cuda::memory_order_relaxed, cuda::memory_order_relaxed
1395 ));
1396
1397 if (evictedFp == EMPTY) {
1398 return true;
1399 }
1400
1401#ifdef CUCKOO_FILTER_COUNT_EVICTIONS
1402 d_numEvictions->fetch_add(1, cuda::memory_order_relaxed);
1403 if (evictionAttempts != nullptr) {
1404 (*evictionAttempts)++;
1405 }
1406#endif
1407
1408 evictions++;
1409 auto [altBucket, altFp] =
1410 getAlternateBucketWithNewFp(currentBucket, evictedFp, numBuckets);
1411 currentBucket = altBucket;
1412 currentFp = altFp;
1413 }
1414
1415 return false;
1416 }
1417
1428 __device__ bool insert(const T& key, uint32_t* evictionAttempts = nullptr) {
1429 auto [i1, i2, fp1, fp2] = getCandidateBucketsAndFPs(key, numBuckets);
1430
1431 // For all policies: fp1 is for bucket i1, fp2 is for bucket i2
1432 // For non-choice-bit policies, fp1 == fp2
1433 if (tryInsertAtBucket(i1, fp1) || tryInsertAtBucket(i2, fp2)) {
1434 return true;
1435 }
1436
1437 // For eviction, use correct fingerprint for the starting bucket
1438 auto startBucket = (fp1 & 1) == 0 ? i1 : i2;
1439 TagType evictFp;
1440
1441 if constexpr (AltBucketPolicy::usesChoiceBit) {
1442 evictFp = (fp1 & 1) == 0 ? fp1 : fp2;
1443 } else {
1444 evictFp = fp1;
1445 }
1446
1448 return insertWithEvictionBFS(evictFp, startBucket, evictionAttempts);
1449 } else if constexpr (Config::evictionPolicy == EvictionPolicy::DFS) {
1450 return insertWithEvictionDFS(evictFp, startBucket, evictionAttempts);
1451 } else {
1452 static_assert(
1455 "Unhandled eviction policy"
1456 );
1457 }
1458 }
1459
1466 __device__ bool contains(const T& key) const {
1467 auto [i1, i2, fp1, fp2] = getCandidateBucketsAndFPs(key, numBuckets);
1468
1469 // fp1 is for bucket i1, fp2 is for bucket i2
1470 // For non-choice-bit policies, fp1 == fp2
1471 return d_buckets[i1].contains(fp1) || d_buckets[i2].contains(fp2);
1472 }
1473
1480 __device__ bool remove(const T& key) {
1481 auto [i1, i2, fp1, fp2] = getCandidateBucketsAndFPs(key, numBuckets);
1482
1483 // fp1 is for bucket i1, fp2 is for bucket i2
1484 // For non-choice-bit policies, fp1 == fp2
1485 return tryRemoveAtBucket(i1, fp1) || tryRemoveAtBucket(i2, fp2);
1486 }
1487};
1488
1489namespace detail {
1490
1491template <typename Config>
1493 const typename Config::KeyType* keys,
1494 bool* output,
1495 size_t n,
1496 Filter<Config>* filter,
1498) {
1499 using BlockReduce = cub::BlockReduce<int32_t, Config::blockSize>;
1500 __shared__ typename BlockReduce::TempStorage tempStorage;
1501
1502 auto idx = globalThreadId();
1503
1504 int32_t success = 0;
1505
1506 if (idx < n) {
1508 success = filter->insert(keys[idx], &threadEvictions);
1509
1510 if (output != nullptr) {
1511 output[idx] = success;
1512 }
1513
1514 if (evictionAttempts != nullptr) {
1516 }
1517 }
1518
1520 __syncthreads();
1521
1522 if (threadIdx.x == 0) {
1523 if (blockSuccessSum > 0) {
1524 filter->d_numOccupied->fetch_add(blockSuccessSum, cuda::memory_order_relaxed);
1525 }
1526 }
1527}
1528
1529template <typename Config>
1531 const typename Config::KeyType* keys,
1532 bool* output,
1533 size_t n,
1534 Filter<Config>* filter
1535) {
1536 auto idx = globalThreadId();
1537
1538 if (idx < n) {
1539 output[idx] = filter->contains(keys[idx]);
1540 }
1541}
1542
1543template <typename Config>
1544__global__ void
1545deleteKernel(const typename Config::KeyType* keys, bool* output, size_t n, Filter<Config>* filter) {
1546 using BlockReduce = cub::BlockReduce<int32_t, Config::blockSize>;
1547 __shared__ typename BlockReduce::TempStorage tempStorage;
1548
1549 auto idx = globalThreadId();
1550
1551 int32_t success = 0;
1552 if (idx < n) {
1553 success = filter->remove(keys[idx]);
1554
1555 if (output != nullptr) {
1556 output[idx] = success;
1557 }
1558 }
1559
1561
1562 if (threadIdx.x == 0 && blockSum > 0) {
1563 filter->d_numOccupied->fetch_sub(blockSum, cuda::memory_order_relaxed);
1564 }
1565}
1566
1567template <typename Config>
1569 const typename Config::KeyType* keys,
1570 typename Filter<Config>::PackedTagType* packedTags,
1571 size_t n,
1572 size_t numBuckets
1573) {
1574 size_t idx = globalThreadId();
1575
1576 if (idx >= n) {
1577 return;
1578 }
1579
1580 using FilterType = Filter<Config>;
1581 using PackedTagType = typename FilterType::PackedTagType;
1582 constexpr size_t bitsPerTag = Config::bitsPerTag;
1583
1584 typename Config::KeyType key = keys[idx];
1585 auto [i1, i2, fp1, fp2] = FilterType::getCandidateBucketsAndFPs(key, numBuckets);
1586
1587 packedTags[idx] =
1588 (static_cast<PackedTagType>(i1) << bitsPerTag) | static_cast<PackedTagType>(fp1);
1589}
1590
1591template <typename Config>
1593 const typename Filter<Config>::PackedTagType* packedTags,
1594 bool* output,
1595 size_t n,
1596 Filter<Config>* filter,
1598) {
1599 using BlockReduce = cub::BlockReduce<int, Config::blockSize>;
1600 __shared__ typename BlockReduce::TempStorage tempStorage;
1601
1602 size_t idx = globalThreadId();
1603
1604 using FilterType = Filter<Config>;
1605 using TagType = typename FilterType::TagType;
1606 using PackedTagType = typename FilterType::PackedTagType;
1607
1608 constexpr size_t bitsPerTag = Config::bitsPerTag;
1609 constexpr TagType fpMask = (1ULL << bitsPerTag) - 1;
1610
1611 int32_t success = 0;
1613 if (idx < n) {
1614 PackedTagType packedTag = packedTags[idx];
1615 size_t primaryBucket = packedTag >> bitsPerTag;
1616 auto fp = static_cast<TagType>(packedTag & fpMask);
1617
1618 if (filter->tryInsertAtBucket(primaryBucket, fp)) {
1619 success = 1;
1620 } else {
1621 auto [i2, fp2] =
1622 FilterType::getAlternateBucketWithNewFp(primaryBucket, fp, filter->numBuckets);
1623
1624 if (filter->tryInsertAtBucket(i2, fp2)) {
1625 success = 1;
1626 } else {
1627 TagType evictFp;
1628 auto startBucket = (fp & 1) == 0 ? primaryBucket : i2;
1629
1630 if constexpr (Config::AltBucketPolicy::usesChoiceBit) {
1631 evictFp = (fp & 1) == 0 ? fp : fp2;
1632 } else {
1633 evictFp = fp;
1634 }
1635
1638 } else if constexpr (Config::evictionPolicy == EvictionPolicy::DFS) {
1640 } else {
1641 static_assert(
1644 "Unhandled eviction policy"
1645 );
1646 }
1647 }
1648 }
1649
1650 if (output != nullptr) {
1651 output[idx] = success;
1652 }
1653
1654 if (evictionAttempts != nullptr) {
1656 }
1657 }
1658
1660
1661 if (threadIdx.x == 0 && blockSum > 0) {
1662 filter->d_numOccupied->fetch_add(blockSum, cuda::memory_order_relaxed);
1663 }
1664}
1665
1666} // namespace detail
1667
1668} // namespace cuckoogpu
A CUDA-accelerated Cuckoo Filter implementation.
cuda::std::atomic< size_t > * d_numOccupied
Pointer to the device memory for the occupancy counter.
static constexpr TagType EMPTY
void containsMany(const thrust::device_vector< T > &d_keys, thrust::device_vector< uint8_t > &d_output, cudaStream_t stream={})
Checks for existence of keys in a Thrust device vector (uint8_t output).
static size_t calculateNumBuckets(size_t capacity)
The number of buckets is enforced to be a power of two in order to allow for efficient modulo on the ...
size_t getNumBuckets() const
Returns the number of buckets in the filter.
static __host__ __device__ uint64_t hash64(const H &key)
Filter(size_t capacity)
Constructs a new Cuckoo Filter.
static constexpr size_t maxEvictions
size_t occupiedSlots()
Returns the total number of occupied slots.
void clear()
Clears the filter, removing all items.
size_t insertMany(const thrust::device_vector< T > &d_keys, thrust::device_vector< uint8_t > &d_output, cudaStream_t stream={})
Inserts keys from a Thrust device vector (uint8_t output).
static constexpr size_t bucketSize
size_t sizeInBytes() const
Returns the size of the filter in bytes.
size_t insertMany(const thrust::device_vector< T > &d_keys, thrust::device_vector< bool > &d_output, cudaStream_t stream={})
Inserts keys from a Thrust device vector.
size_t numBuckets
Number of buckets in the filter.
size_t insertManySorted(const thrust::device_vector< T > &d_keys, cudaStream_t stream={})
Inserts keys from a Thrust device vector, sorting them first, without outputting results.
static constexpr size_t fpMask
__device__ bool tryInsertAtBucket(size_t bucketIdx, TagType tag)
Attempts to insert a tag into a specific bucket.
size_t deleteMany(const T *d_keys, const size_t n, bool *d_output=nullptr, cudaStream_t stream={})
Tries to remove a set of keys from the filter.
size_t insertManySorted(const thrust::device_vector< T > &d_keys, thrust::device_vector< uint8_t > &d_output, cudaStream_t stream={})
Inserts keys from a Thrust device vector, sorting them first (uint8_t output).
float loadFactor()
Calculates the current load factor of the filter.
typename std::conditional< bitsPerTag<=8, uint32_t, uint64_t >::type PackedTagType
static __host__ __device__ size_t getAlternateBucket(size_t bucket, TagType fp, size_t numBuckets)
Computes the alternate bucket for a fingerprint.
size_t deleteCasAttempts()
Returns the total number of delete CAS attempts (compare_exchange calls).
size_t insertMany(const thrust::device_vector< T > &d_keys, cudaStream_t stream={})
Inserts keys from a Thrust device vector without outputting results.
Filter(const Filter &)=delete
__device__ bool insert(const T &key, uint32_t *evictionAttempts=nullptr)
Inserts a single key into the filter.
Bucket * d_buckets
Pointer to the device memory for the buckets.
cuda::std::atomic< size_t > * d_deleteCasFailures
Pointer to device memory for the failed delete CAS counter.
size_t countOccupiedSlots()
Counts occupied slots by iterating over all buckets on the host.
typename Config::AltBucketPolicy AltBucketPolicy
static __host__ __device__ cuda::std::tuple< size_t, TagType > getAlternateBucketWithNewFp(size_t bucket, TagType fp, size_t numBuckets)
Computes alternate bucket AND updated fingerprint for choice bit policies.
void containsMany(const thrust::device_vector< T > &d_keys, thrust::device_vector< bool > &d_output, cudaStream_t stream={})
Checks for existence of keys in a Thrust device vector.
typename Config::TagType TagType
static __host__ __device__ cuda::std::tuple< size_t, size_t, TagType, TagType > getCandidateBucketsAndFPs(const T &key, size_t numBuckets)
__device__ bool tryRemoveAtBucket(size_t bucketIdx, TagType tag)
Attempt to remove a single instance of a fingerprint from a bucket.
__device__ bool remove(const T &key)
Removes a key from the filter.
size_t deleteMany(const thrust::device_vector< T > &d_keys, cudaStream_t stream={})
Deletes keys in a Thrust device vector without outputting results.
size_t deleteMany(const thrust::device_vector< T > &d_keys, thrust::device_vector< bool > &d_output, cudaStream_t stream={})
Deletes keys in a Thrust device vector.
static constexpr size_t tagEntryBytes
size_t deleteCasFailures()
Returns the total number of failed delete CAS attempts.
void containsMany(const T *d_keys, const size_t n, bool *d_output, cudaStream_t stream={})
Checks for the existence of a batch of keys.
__device__ bool insertWithEvictionDFS(TagType fp, size_t startBucket, uint32_t *evictionAttempts=nullptr)
Inserts a fingerprint into the filter by evicting existing fingerprints.
static constexpr size_t bitsPerTag
__device__ bool insertWithEvictionBFS(TagType fp, size_t startBucket, uint32_t *evictionAttempts=nullptr)
Inserts a fingerprint using repeated shallow breadth-first attempts.
static constexpr size_t blockSize
size_t deleteMany(const thrust::device_vector< T > &d_keys, thrust::device_vector< uint8_t > &d_output, cudaStream_t stream={})
Deletes keys in a Thrust device vector (uint8_t output).
~Filter()
Destroys the Cuckoo Filter.
size_t insertManySorted(const T *d_keys, const size_t n, bool *d_output=nullptr, cudaStream_t stream={})
This pre-sorts the input keys based on the primary bucket index to allow for coalesced memory access ...
__device__ bool contains(const T &key) const
Checks if a key exists in the filter.
cuda::std::atomic< size_t > * d_deleteCasAttempts
Pointer to device memory for the delete CAS attempt counter.
void resetDeleteCasCounters()
Resets the delete CAS counters to zero.
size_t insertManySorted(const thrust::device_vector< T > &d_keys, thrust::device_vector< bool > &d_output, cudaStream_t stream={})
Inserts keys from a Thrust device vector, sorting them first.
size_t insertMany(const T *d_keys, const size_t n, bool *d_output=nullptr, cudaStream_t stream={})
Inserts a batch of keys into the filter.
Filter & operator=(const Filter &)=delete
size_t h_numOccupied
Number of occupied buckets in the filter.
size_t capacity()
Returns the total capacity of the filter.
typename Config::KeyType T
#define SDIV(x, y)
Integer division with rounding up (ceiling).
Definition helpers.cuh:178
#define CUCKOO_CUDA_CALL(err)
Macro for checking CUDA errors.
Definition helpers.cuh:184
__global__ void deleteKernel(const typename Config::KeyType *keys, bool *output, size_t n, Filter< Config > *filter)
Kernel for deleting keys.
__host__ __device__ __forceinline__ constexpr bool hasZero(WordType v)
Checks if a packed word contains a zero slot.
Definition helpers.cuh:96
__host__ __device__ __forceinline__ uint32_t globalThreadId()
Calculates the global thread ID in a 1D grid.
Definition helpers.cuh:22
constexpr bool powerOfTwo(size_t n)
Checks if a number is a power of two.
Definition helpers.cuh:14
__global__ void computePackedTagsKernel(const typename Config::KeyType *keys, typename Filter< Config >::PackedTagType *packedTags, size_t n, size_t numBuckets)
Kernel for computing packed tags for sorting.
__global__ void containsKernel(const typename Config::KeyType *keys, bool *output, size_t n, Filter< Config > *filter)
Kernel for checking existence of keys.
__global__ void insertKernelSorted(const typename Filter< Config >::PackedTagType *packedTags, bool *output, size_t n, Filter< Config > *filter, uint32_t *evictionAttempts)
Kernel for inserting pre-sorted keys into the filter.
__global__ void insertKernel(const typename Config::KeyType *keys, bool *output, size_t n, Filter< Config > *filter, uint32_t *evictionAttempts)
Kernel for inserting keys into the filter.
EvictionPolicy
Eviction policy for the Cuckoo Filter.
@ BFS
Breadth-first search (default)
@ DFS
Pure depth-first search.
Configuration structure for the Cuckoo Filter.
static constexpr size_t bucketSize
static constexpr size_t blockSize
static constexpr size_t maxEvictions
static constexpr size_t bitsPerTag
static constexpr EvictionPolicy evictionPolicy
typename std::conditional< bitsPerTag<=8, uint8_t, typename std::conditional< bitsPerTag<=16, uint16_t, uint32_t >::type >::type TagType
AltBucketPolicy_< KeyType, TagType, bitsPerTag, bucketSize_ > AltBucketPolicy
Bucket structure that holds the fingerprint and tags for a given bucket.
__host__ __device__ __forceinline__ TagType extractTag(WordType packed, size_t tagIdx) const
typename Config::WordType WordType
__host__ __device__ __forceinline__ WordType replaceTag(WordType packed, size_t tagIdx, TagType newTag) const
static constexpr size_t tagsPerWord
__device__ __forceinline__ void load128Bit(size_t startIdx, WordType(&out)[N]) const
Loads words using 128-bit vectorized loads into a fixed-size array.
static constexpr size_t wordCount
__device__ static __forceinline__ bool checkWords(const WordType(&loaded)[N], WordType replicatedTag)
Checks an array of loaded words for a matching tag using SWAR.
cuda::std::atomic< WordType > packedTags[wordCount]
__device__ bool contains(TagType tag) const
Checks if a tag is present in the bucket using vectorized loads.
This is used by the sorted insert kernel to store the fingerprint and primary bucket index in a compa...
__host__ __device__ void setFingerprint(TagType fp)
static constexpr PackedTagType fpMask
__host__ __device__ uint64_t getBucketIndex() const
static constexpr size_t fpBits
__host__ __device__ void setBucketIdx(size_t bucketIdx)
static constexpr PackedTagType bucketIdxMask
__host__ __device__ PackedTag()
static constexpr size_t bucketIdxBits
static constexpr size_t totalBits
__host__ __device__ TagType getFingerprint() const
__host__ __device__ PackedTag(TagType fp, uint64_t bucketIdx)