| @ -0,0 +1,208 @@ | |||
| #pragma once | |||
| #include <stdint.h> | |||
| #include <atomic> | |||
| #include <gp/array.hpp> | |||
| #include <gp/algorithm/tmp_manip.hpp> | |||
| #include <gp/exception.hpp> | |||
| namespace gp { | |||
| struct linear_skipstone { | |||
| constexpr static size_t ccbits = 1; | |||
| constexpr static size_t cclast = 1; | |||
| size_t ret = 0; | |||
| void next() { | |||
| ++ret; | |||
| } | |||
| operator size_t() { | |||
| return ret; | |||
| } | |||
| }; | |||
| struct stepcache_skipstone { | |||
| constexpr static size_t ccbits = 7; | |||
| constexpr static size_t cclast = 127; | |||
| constexpr static size_t list[128] = { | |||
| 0, 1, 2, 3, | |||
| 8, 9, 10, 11, | |||
| 20, 21, 22, 23, | |||
| 36, 37, 38, 39, | |||
| 56, 57, 58, 59, | |||
| 80, 81, 82, 83, | |||
| 108, 109, 110, 111, | |||
| 140, 141, 142, 143, | |||
| 176, 177, 178, 179, | |||
| 216, 217, 218, 219, | |||
| 260, 261, 262, 263, | |||
| 308, 309, 310, 311, | |||
| 360, 361, 362, 363, | |||
| 416, 417, 418, 419, | |||
| 476, 477, 478, 479, | |||
| 540, 541, 542, 543, | |||
| 608, 609, 610, 611, | |||
| 680, 681, 682, 683, | |||
| 756, 757, 758, 759, | |||
| 836, 837, 838, 839, | |||
| 920, 921, 922, 923, | |||
| 1008, 1009, 1010, 1011, | |||
| 1100, 1101, 1102, 1103, | |||
| 1196, 1197, 1198, 1199, | |||
| 1296, 1297, 1298, 1299, | |||
| 1400, 1401, 1402, 1403, | |||
| 1508, 1509, 1510, 1511, | |||
| 1736, 1737, 1738, 1739, | |||
| 1856, 1857, 1858, 1859, | |||
| 1980, 1981, 1982, 1983, | |||
| 2108, 2109, 2110, 2111, | |||
| 2240, 2241, 2242, 2243 | |||
| }; | |||
| size_t ret = 0; | |||
| void next() { | |||
| ++ret; | |||
| } | |||
| operator size_t() { | |||
| [[unlikely]] if ( ret >= 128 ) | |||
| return ret+list[127]; | |||
| return list[ret]; | |||
| } | |||
| }; | |||
| template<typename hash_type = uint32_t, uint8_t magnitude = 20, uint8_t remainder = (8*sizeof(hash_type))-magnitude, typename skipstone_t = linear_skipstone> | |||
| class quotient_filter { | |||
| constexpr static size_t phys_size = (1 << magnitude) / 32; | |||
| using rem_t = typename gp::either<(remainder<=16), uint16_t, uint32_t>::type; | |||
| struct node{ | |||
| //rem_t ccount : skipstone_t::ccbits; | |||
| rem_t is_occupied : 1; | |||
| rem_t is_deleted : 1; | |||
| rem_t r : remainder; | |||
| }; | |||
| gp::array< | |||
| node, | |||
| 1 << magnitude | |||
| > data; | |||
| void skipstone_killswitch(size_t q, size_t skip) | |||
| { | |||
| if(q == (q+skip)%data.size()) | |||
| { | |||
| if constexpr (gp_config::has_exceptions) | |||
| { | |||
| throw gp::runtime_error("infinite skiploop detected"); | |||
| } | |||
| else | |||
| { | |||
| exit((int)gp_errorcodes::infinite_skipstone); | |||
| } | |||
| } | |||
| } | |||
| public: | |||
| void set_hash(hash_type v) | |||
| { | |||
| const size_t q = v & ((1 << magnitude)-1); | |||
| const size_t r = (v >> magnitude) & ((1 << remainder)-1); | |||
| skipstone_t skip; | |||
| for(;;) | |||
| { | |||
| auto& slot = data[(q+skip)%data.size()]; | |||
| if(slot.is_occupied) | |||
| { | |||
| if(slot.is_deleted) | |||
| { | |||
| slot.r = r; | |||
| slot.is_deleted = false; | |||
| for(;;) | |||
| { | |||
| skip.next(); | |||
| slot = data[(q+skip)%data.size()]; | |||
| if(!slot.is_occupied) | |||
| { | |||
| return; | |||
| } | |||
| else if(slot.r == r) | |||
| { | |||
| slot.is_deleted = true; | |||
| } | |||
| skipstone_killswitch(q, skip); | |||
| } | |||
| } | |||
| if(slot.r == r) | |||
| { | |||
| return; | |||
| } | |||
| skip.next(); | |||
| } | |||
| else | |||
| { | |||
| slot.r = r; | |||
| slot.is_occupied = true; | |||
| slot.is_deleted = false; | |||
| return; | |||
| } | |||
| skipstone_killswitch(q, skip); | |||
| } | |||
| } | |||
| bool test_hash(hash_type v) | |||
| { | |||
| const size_t q = v & ((1 << magnitude)-1); | |||
| const size_t r = (v >> magnitude) & ((1 << remainder)-1); | |||
| skipstone_t skip; | |||
| for(;;) | |||
| { | |||
| auto& slot = data[(q+skip)%data.size()]; | |||
| if(slot.is_occupied) | |||
| { | |||
| if(!slot.is_deleted) | |||
| { | |||
| if(slot.r == r) | |||
| { | |||
| return true; | |||
| } | |||
| } | |||
| skip.next(); | |||
| } | |||
| else | |||
| { | |||
| return false; | |||
| } | |||
| skipstone_killswitch(q, skip); | |||
| } | |||
| } | |||
| void remove_hash(hash_type v) | |||
| { | |||
| const size_t q = v & ((1 << magnitude)-1); | |||
| const size_t r = (v >> magnitude) & ((1 << remainder)-1); | |||
| skipstone_t skip; | |||
| for(;;) | |||
| { | |||
| auto& slot = data[(q+skip)%data.size()]; | |||
| if(slot.is_occupied) | |||
| { | |||
| if(!slot.is_deleted) | |||
| { | |||
| if(slot.r == r) | |||
| { | |||
| slot.is_deleted = true; | |||
| } | |||
| } | |||
| skip.next(); | |||
| } | |||
| else | |||
| { | |||
| return; | |||
| } | |||
| skipstone_killswitch(q, skip); | |||
| } | |||
| } | |||
| }; | |||
| } | |||
| @ -0,0 +1,127 @@ | |||
| #include "test_scaffold.h" | |||
| #include <random> | |||
| #include <string> | |||
| #include "gp/quotient_filter.hpp" | |||
| typedef std::linear_congruential_engine<uint_fast64_t, 128273, 13, 2147483647> cheap_rand; | |||
| typedef std::linear_congruential_engine<uint_fast64_t, 69857, 87541, 2147483647> cheap_rand_bis; | |||
| struct qfilter_test : public test_scaffold { | |||
| uint32_t seed; | |||
| qfilter_test() { | |||
| seed = std::random_device{}(); | |||
| name = __FILE__ ":1_seed"; | |||
| name += std::to_string(seed); | |||
| } | |||
| virtual int run() { | |||
| cheap_rand setter(seed); | |||
| cheap_rand getter(seed); | |||
| gp::quotient_filter test_filter; | |||
| for(int a = 0 ; a < 100; a++) | |||
| { | |||
| test_filter.set_hash(setter()); | |||
| } | |||
| bool result = true; | |||
| for(int a = 0 ; a < 100; a++) | |||
| { | |||
| result *= test_filter.test_hash(getter()); | |||
| } | |||
| return !result; | |||
| } | |||
| }; | |||
| append_test dummy_rshyr43(new qfilter_test{}); | |||
| struct qfilter2_test : public test_scaffold { | |||
| uint32_t seedA; | |||
| uint32_t seedB; | |||
| qfilter2_test() { | |||
| seedA = std::random_device{}(); | |||
| seedB = std::random_device{}(); | |||
| name = __FILE__ ":2_seedA"; | |||
| name += std::to_string(seedA); | |||
| name += "&seedB"; | |||
| name += std::to_string(seedB); | |||
| } | |||
| virtual int run() { | |||
| cheap_rand setter(seedA); | |||
| cheap_rand deleter(seedA); | |||
| cheap_rand_bis getter(seedB); | |||
| gp::quotient_filter test_filter; | |||
| for(int a = 0 ; a < 100000; a++) | |||
| { | |||
| test_filter.set_hash(setter()); | |||
| } | |||
| int interference = 0; | |||
| for(int a = 0 ; a < 100000; a++) | |||
| { | |||
| interference += test_filter.test_hash(getter()); | |||
| } | |||
| for(int a = 0 ; a < 100000; a++) | |||
| { | |||
| test_filter.remove_hash(deleter()); | |||
| } | |||
| return interference > 25; | |||
| } | |||
| }; | |||
| append_test dummy_kegdflu3(new qfilter2_test{}); | |||
| struct qfilter3_test : public test_scaffold { | |||
| uint32_t seedA; | |||
| uint32_t seedB; | |||
| qfilter3_test() { | |||
| seedA = std::random_device{}(); | |||
| seedB = std::random_device{}(); | |||
| name = __FILE__ ":3_seedA"; | |||
| name += std::to_string(seedA); | |||
| name += "&seedB"; | |||
| name += std::to_string(seedB); | |||
| } | |||
| virtual int run() { | |||
| cheap_rand setter(seedA); | |||
| cheap_rand deleter(seedA); | |||
| cheap_rand_bis getter(seedB); | |||
| gp::quotient_filter<uint32_t, 20,12, gp::stepcache_skipstone> test_filter; | |||
| for(int a = 0 ; a < 100000; a++) | |||
| { | |||
| test_filter.set_hash(setter()); | |||
| } | |||
| int interference = 0; | |||
| for(int a = 0 ; a < 100000; a++) | |||
| { | |||
| interference += test_filter.test_hash(getter()); | |||
| } | |||
| for(int a = 0 ; a < 100000; a++) | |||
| { | |||
| test_filter.remove_hash(deleter()); | |||
| } | |||
| return interference > 25; | |||
| } | |||
| }; | |||
| append_test dummy_kjdflu3(new qfilter3_test{}); | |||