diff --git a/CMakeLists.txt b/CMakeLists.txt index dc1d4e9..6cf408e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,8 +15,8 @@ target_include_directories(bloom_filter PUBLIC include) # doesn't matter for this specific project though target_compile_features(bloom_filter PUBLIC cxx_std_20) -add_executable(stats_basic_bloom_filter - example/stats_basic_bloom_filter.cpp +add_executable(bloom_filter_stats_csv + plots/data/bloom_filter_stats_csv.cpp ) -target_link_libraries(stats_basic_bloom_filter PRIVATE bloom_filter) +target_link_libraries(bloom_filter_stats_csv PRIVATE bloom_filter) diff --git a/DEV.md b/DEV.md index 3c26895..4159ef8 100644 --- a/DEV.md +++ b/DEV.md @@ -127,3 +127,14 @@ bits filled fraction is P(any arbitrary bit is 1) False positive = all of those bits end up being 1 for some hash = P^k This has the same independence issue in assumption though. + +# Plotting stuff + +Found out about gnuplot which I guess is a bit of an overkill having a binary for the whole thing +but fine the plotting itself is easier. In the end the plots came out in line with theoretical which +was great. + +# Links + +There are many variations for bloom filters that I'll get to implementing and testing _eventually_ which +I hope is soon enough. diff --git a/example/stats_basic_bloom_filter.cpp b/example/stats_basic_bloom_filter.cpp deleted file mode 100644 index 1fe0c4d..0000000 --- a/example/stats_basic_bloom_filter.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "bloom_filter/bloom_filter.hpp" -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char *argv[]) { - if (argc != 4) { // REM: 0th ars it the program name - std::cerr << "need args n(number of keys inserted) m(filter size in " - "num bits) k(num hash funcs)"; - return 1; - } - - uint n = std::stoul(argv[1]); - uint m = std::stoul(argv[2]); - uint k = std::stoul(argv[3]); - - // std::random_device rd; you can uncomment and pass it in here to randomise - std::mt19937_64 rng{}; // no seed so this is deterministic - const uint TOTAL_TRIALS = 20; - - double sum_theorital_approx_fp_rate = 0; - double sum_filter_approx_fp_rate = 0; - double sum_actual_fp_rate = 0; - - for (int _t = 0; _t < TOTAL_TRIALS; _t++) { - uint64_t seed1 = rng(), seed2 = rng(); - - BloomFilter filter{m, k, seed1, seed2}; - - std::set actual_inserts; - for (int i = 0; i < n; i++) { - uint64_t num = rng(); - actual_inserts.insert(num); - filter.put(&num, sizeof(num)); - } - - const uint ELEMS_CHECK = 10000; - uint fp_count = 0; - for (int i = 0; i < ELEMS_CHECK; i++) { - uint64_t test = rng(); - bool actually_present = actual_inserts.count(test); - bool filter_contains = filter.may_contain(&test, sizeof(test)); - fp_count += !actually_present && filter_contains; - } - - double theoretical_approx_fp_rate = - std::pow(1 - std::exp(-static_cast(k) * n / m), k); - double filter_approx_fp_rate = filter.false_positive_probability(); - double actual_fp_rate = static_cast(fp_count) / ELEMS_CHECK; - - sum_theorital_approx_fp_rate += theoretical_approx_fp_rate; - sum_filter_approx_fp_rate += filter_approx_fp_rate; - sum_actual_fp_rate += actual_fp_rate; - } - - std::printf("simple bloom filter with n = %d, m = %d, k = %d\n", n, m, k); - std::printf("theoretical fp probs : %.8f\n", - sum_theorital_approx_fp_rate / TOTAL_TRIALS); - std::printf("filter reported fp probs : %.8f\n", - sum_filter_approx_fp_rate / TOTAL_TRIALS); - std::printf("actual fp rate : %.8f\n", sum_actual_fp_rate / TOTAL_TRIALS); - - return 0; -} diff --git a/plots/data/bloom_filter_stats.csv b/plots/data/bloom_filter_stats.csv new file mode 100644 index 0000000..ce40623 --- /dev/null +++ b/plots/data/bloom_filter_stats.csv @@ -0,0 +1,42 @@ +n,m,k,theoretical_fpp,filter_fpp,actual_fpp +10000,100000,2,0.03285854,0.03289392,0.03292500 +10000,100000,3,0.01741059,0.01743016,0.01755500 +10000,100000,4,0.01181327,0.01185258,0.01231500 +10000,100000,5,0.00943093,0.00940946,0.00898000 +10000,100000,6,0.00843621,0.00844959,0.00851000 +10000,100000,7,0.00819372,0.00818934,0.00823000 +10000,100000,8,0.00845547,0.00843888,0.00853000 +10000,100000,9,0.00912699,0.00915585,0.00945500 +10000,100000,10,0.01018589,0.01023808,0.01031500 +10000,100000,11,0.01164950,0.01163213,0.01149000 +10000,100000,12,0.01356057,0.01344405,0.01366000 +10000,100000,13,0.01598020,0.01592878,0.01558500 +10000,100000,14,0.01898380,0.01903601,0.01829000 +10000,100000,15,0.02265812,0.02262230,0.02297000 +1000,100000,5,0.00000028,0.00000028,0.00000000 +2000,100000,5,0.00000780,0.00000782,0.00001500 +3000,100000,5,0.00005244,0.00005258,0.00004000 +4000,100000,5,0.00019571,0.00019651,0.00019500 +5000,100000,5,0.00052956,0.00053070,0.00050500 +7500,100000,5,0.00299029,0.00298418,0.00307500 +10000,100000,5,0.00943093,0.00944119,0.00924000 +15000,100000,5,0.04089419,0.04091604,0.04042500 +20000,100000,5,0.10092519,0.10124248,0.10181000 +30000,100000,5,0.28297059,0.28242511,0.28079500 +40000,100000,5,0.48332436,0.48360202,0.48378500 +60000,100000,5,0.77464850,0.77395500,0.77450000 +80000,100000,5,0.91171555,0.91254904,0.91244000 +10000,50000,5,0.10092519,0.10099464,0.10038000 +10000,60000,5,0.05778111,0.05778597,0.05903000 +10000,70000,5,0.03465784,0.03474584,0.03495000 +10000,80000,5,0.02167922,0.02172916,0.02183500 +10000,90000,5,0.01407034,0.01406240,0.01391000 +10000,100000,5,0.00943093,0.00943771,0.00954000 +10000,125000,5,0.00389460,0.00388819,0.00400500 +10000,150000,5,0.00183031,0.00183603,0.00199000 +10000,175000,5,0.00094805,0.00094767,0.00087000 +10000,200000,5,0.00052956,0.00053068,0.00055500 +10000,250000,5,0.00019571,0.00019591,0.00028500 +10000,300000,5,0.00008527,0.00008530,0.00009500 +10000,400000,5,0.00002240,0.00002242,0.00001500 +10000,600000,5,0.00000327,0.00000327,0.00000500 diff --git a/plots/data/bloom_filter_stats_csv.cpp b/plots/data/bloom_filter_stats_csv.cpp new file mode 100644 index 0000000..0325fd1 --- /dev/null +++ b/plots/data/bloom_filter_stats_csv.cpp @@ -0,0 +1,85 @@ +#include "bloom_filter/bloom_filter.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + if (argc != 2) { + std::cerr << "usage: " << argv[0] << " plots/cases.csv\n"; + return 1; + } + + std::ifstream csv{argv[1]}; + std::string line; + std::getline(csv, line); // skipping header n,m,k + + std::mt19937_64 rng{}; // no seed so this is deterministic + constexpr uint32_t TOTAL_TRIALS = 20; + constexpr uint32_t ELEMS_CHECK = 10000; + + std::printf("n,m,k,theoretical_fpp,filter_fpp,actual_fpp\n"); + + while (std::getline(csv, line)) { + if (line.empty()) { + continue; + } + + std::stringstream ss(line); + std::string n_str, m_str, k_str; + std::getline(ss, n_str, ','); + std::getline(ss, m_str, ','); + std::getline(ss, k_str, ','); + + uint64_t n = std::stoull(n_str); + uint64_t m = std::stoull(m_str); + uint32_t k = std::stoul(k_str); + + double sum_theoretical_fpp = 0; + double sum_filter_fpp = 0; + double sum_actual_fpp = 0; + + for (uint32_t trial = 0; trial < TOTAL_TRIALS; trial++) { + uint64_t seed1 = rng(), seed2 = rng(); + BloomFilter filter{static_cast(m), k, seed1, seed2}; + + std::unordered_set actual_inserts; + for (uint64_t i = 0; i < n; i++) { + uint64_t num = rng(); + actual_inserts.insert(num); + filter.put(&num, sizeof(num)); + } + + uint32_t fp_count = 0; + for (uint32_t i = 0; i < ELEMS_CHECK; i++) { + uint64_t test = rng(); + bool actually_present = actual_inserts.count(test); + bool filter_contains = filter.may_contain(&test, sizeof(test)); + fp_count += !actually_present && filter_contains; + } + + double theoretical_fpp = + std::pow(1 - std::exp(-static_cast(k) * n / m), k); + double filter_fpp = filter.false_positive_probability(); + double actual_fpp = static_cast(fp_count) / ELEMS_CHECK; + + sum_theoretical_fpp += theoretical_fpp; + sum_filter_fpp += filter_fpp; + sum_actual_fpp += actual_fpp; + } + + std::printf("%llu,%llu,%u,%.8f,%.8f,%.8f\n", + static_cast(n), + static_cast(m), k, + sum_theoretical_fpp / TOTAL_TRIALS, + sum_filter_fpp / TOTAL_TRIALS, + sum_actual_fpp / TOTAL_TRIALS); + } + + return 0; +} diff --git a/plots/data/cases.csv b/plots/data/cases.csv new file mode 100644 index 0000000..bfda434 --- /dev/null +++ b/plots/data/cases.csv @@ -0,0 +1,42 @@ +n,m,k +10000,100000,2 +10000,100000,3 +10000,100000,4 +10000,100000,5 +10000,100000,6 +10000,100000,7 +10000,100000,8 +10000,100000,9 +10000,100000,10 +10000,100000,11 +10000,100000,12 +10000,100000,13 +10000,100000,14 +10000,100000,15 +1000,100000,5 +2000,100000,5 +3000,100000,5 +4000,100000,5 +5000,100000,5 +7500,100000,5 +10000,100000,5 +15000,100000,5 +20000,100000,5 +30000,100000,5 +40000,100000,5 +60000,100000,5 +80000,100000,5 +10000,50000,5 +10000,60000,5 +10000,70000,5 +10000,80000,5 +10000,90000,5 +10000,100000,5 +10000,125000,5 +10000,150000,5 +10000,175000,5 +10000,200000,5 +10000,250000,5 +10000,300000,5 +10000,400000,5 +10000,600000,5 diff --git a/plots/gnuplot/fpp_vs_k.gp b/plots/gnuplot/fpp_vs_k.gp new file mode 100644 index 0000000..337b439 --- /dev/null +++ b/plots/gnuplot/fpp_vs_k.gp @@ -0,0 +1,13 @@ +set datafile separator comma +set terminal pngcairo size 1000,700 enhanced font "Arial,12" +set output "plots/png/fpp_vs_k.png" + +set title "Bloom filter false-positive rate vs k (n=10000, m=100000)" +set xlabel "hash functions (k)" +set ylabel "false-positive rate" +set grid +set key outside + +plot "plots/data/bloom_filter_stats.csv" using (($1 == 10000 && $2 == 100000) ? $3 : 1/0):4 every ::1 with linespoints title "theoretical", \ + "plots/data/bloom_filter_stats.csv" using (($1 == 10000 && $2 == 100000) ? $3 : 1/0):5 every ::1 with linespoints title "filter estimate", \ + "plots/data/bloom_filter_stats.csv" using (($1 == 10000 && $2 == 100000) ? $3 : 1/0):6 every ::1 with linespoints title "observed" diff --git a/plots/gnuplot/fpp_vs_m.gp b/plots/gnuplot/fpp_vs_m.gp new file mode 100644 index 0000000..8be5d55 --- /dev/null +++ b/plots/gnuplot/fpp_vs_m.gp @@ -0,0 +1,16 @@ +set datafile separator comma +set terminal pngcairo size 1000,700 enhanced font "Arial,12" +set output "plots/png/fpp_vs_m.png" + +set title "Bloom filter false-positive rate vs m (n=10000, k=5)" +set xlabel "filter size in bits (m)" +set ylabel "false-positive rate" +set logscale x 10 +set logscale y 10 +set yrange [1e-5:0.2] +set grid +set key outside + +plot "plots/data/bloom_filter_stats.csv" using (($1 == 10000 && $3 == 5) ? $2 : 1/0):4 every ::1 with linespoints title "theoretical", \ + "plots/data/bloom_filter_stats.csv" using (($1 == 10000 && $3 == 5) ? $2 : 1/0):5 every ::1 with linespoints title "filter estimate", \ + "plots/data/bloom_filter_stats.csv" using (($1 == 10000 && $3 == 5) ? $2 : 1/0):6 every ::1 with linespoints title "observed" diff --git a/plots/gnuplot/fpp_vs_n.gp b/plots/gnuplot/fpp_vs_n.gp new file mode 100644 index 0000000..b303f41 --- /dev/null +++ b/plots/gnuplot/fpp_vs_n.gp @@ -0,0 +1,14 @@ +set datafile separator comma +set terminal pngcairo size 1000,700 enhanced font "Arial,12" +set output "plots/png/fpp_vs_n.png" + +set title "Bloom filter false-positive rate vs n (m=100000, k=5)" +set xlabel "inserted keys (n)" +set ylabel "false-positive rate" +set logscale x 10 +set grid +set key outside + +plot "plots/data/bloom_filter_stats.csv" using (($2 == 100000 && $3 == 5) ? $1 : 1/0):4 every ::1 with linespoints title "theoretical", \ + "plots/data/bloom_filter_stats.csv" using (($2 == 100000 && $3 == 5) ? $1 : 1/0):5 every ::1 with linespoints title "filter estimate", \ + "plots/data/bloom_filter_stats.csv" using (($2 == 100000 && $3 == 5) ? $1 : 1/0):6 every ::1 with linespoints title "observed" diff --git a/plots/png/fpp_vs_k.png b/plots/png/fpp_vs_k.png new file mode 100644 index 0000000..76de635 Binary files /dev/null and b/plots/png/fpp_vs_k.png differ diff --git a/plots/png/fpp_vs_m.png b/plots/png/fpp_vs_m.png new file mode 100644 index 0000000..56dbbda Binary files /dev/null and b/plots/png/fpp_vs_m.png differ diff --git a/plots/png/fpp_vs_n.png b/plots/png/fpp_vs_n.png new file mode 100644 index 0000000..f47a300 Binary files /dev/null and b/plots/png/fpp_vs_n.png differ