summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
authorPeter Dillinger <peterd@fb.com>2020-11-12 20:45:02 -0800
committerFacebook GitHub Bot <facebook-github-bot@users.noreply.github.com>2020-11-12 20:46:14 -0800
commit60af9643728ce669e4695f7b2658f8d276a7fcd9 (patch)
tree9d0a917fa82fba4a49554b07b290975d425fc18a /util
parentbbbb5a280d2dc888e8238e24425ef02655af868f (diff)
Experimental (production candidate) SST schema for Ribbon filter (#7658)
Summary: Added experimental public API for Ribbon filter: NewExperimentalRibbonFilterPolicy(). This experimental API will take a "Bloom equivalent" bits per key, and configure the Ribbon filter for the same FP rate as Bloom would have but ~30% space savings. (Note: optimize_filters_for_memory is not yet implemented for Ribbon filter. That can be added with no effect on schema.) Internally, the Ribbon filter is configured using a "one_in_fp_rate" value, which is 1 over desired FP rate. For example, use 100 for 1% FP rate. I'm expecting this will be used in the future for configuring Bloom-like filters, as I expect people to more commonly hold constant the filter accuracy and change the space vs. time trade-off, rather than hold constant the space (per key) and change the accuracy vs. time trade-off, though we might make that available. ### Benchmarking ``` $ ./filter_bench -impl=2 -quick -m_keys_total_max=200 -average_keys_per_filter=100000 -net_includes_hashing Building... Build avg ns/key: 34.1341 Number of filters: 1993 Total size (MB): 238.488 Reported total allocated memory (MB): 262.875 Reported internal fragmentation: 10.2255% Bits/key stored: 10.0029 ---------------------------- Mixed inside/outside queries... Single filter net ns/op: 18.7508 Random filter net ns/op: 258.246 Average FP rate %: 0.968672 ---------------------------- Done. (For more info, run with -legend or -help.) $ ./filter_bench -impl=3 -quick -m_keys_total_max=200 -average_keys_per_filter=100000 -net_includes_hashing Building... Build avg ns/key: 130.851 Number of filters: 1993 Total size (MB): 168.166 Reported total allocated memory (MB): 183.211 Reported internal fragmentation: 8.94626% Bits/key stored: 7.05341 ---------------------------- Mixed inside/outside queries... Single filter net ns/op: 58.4523 Random filter net ns/op: 363.717 Average FP rate %: 0.952978 ---------------------------- Done. (For more info, run with -legend or -help.) ``` 168.166 / 238.488 = 0.705 -> 29.5% space reduction 130.851 / 34.1341 = 3.83x construction time for this Ribbon filter vs. lastest Bloom filter (could make that as little as about 2.5x for less space reduction) ### Working around a hashing "flaw" bloom_test discovered a flaw in the simple hashing applied in StandardHasher when num_starts == 1 (num_slots == 128), showing an excessively high FP rate. The problem is that when many entries, on the order of number of hash bits or kCoeffBits, are associated with the same start location, the correlation between the CoeffRow and ResultRow (for efficiency) can lead to a solution that is "universal," or nearly so, for entries mapping to that start location. (Normally, variance in start location breaks the effective association between CoeffRow and ResultRow; the same value for CoeffRow is effectively different if start locations are different.) Without kUseSmash and with num_starts > 1 (thus num_starts ~= num_slots), this flaw should be completely irrelevant. Even with 10M slots, the chances of a single slot having just 16 (or more) entries map to it--not enough to cause an FP problem, which would be local to that slot if it happened--is 1 in millions. This spreadsheet formula shows that: =1/(10000000*(1 - POISSON(15, 1, TRUE))) As kUseSmash==false (the setting for Standard128RibbonBitsBuilder) is intended for CPU efficiency of filters with many more entries/slots than kCoeffBits, a very reasonable work-around is to disallow num_starts==1 when !kUseSmash, by making the minimum non-zero number of slots 2*kCoeffBits. This is the work-around I've applied. This also means that the new Ribbon filter schema (Standard128RibbonBitsBuilder) is not space-efficient for less than a few hundred entries. Because of this, I have made it fall back on constructing a Bloom filter, under existing schema, when that is more space efficient for small filters. (We can change this in the future if we want.) TODO: better unit tests for this case in ribbon_test, and probably update StandardHasher for kUseSmash case so that it can scale nicely to small filters. ### Other related changes * Add Ribbon filter to stress/crash test * Add Ribbon filter to filter_bench as -impl=3 * Add option string support, as in "filter_policy=experimental_ribbon:5.678;" where 5.678 is the Bloom equivalent bits per key. * Rename internal mode BloomFilterPolicy::kAuto to kAutoBloom * Add a general BuiltinFilterBitsBuilder::CalculateNumEntry based on binary searching CalculateSpace (inefficient), so that subclasses (especially experimental ones) don't have to provide an efficient implementation inverting CalculateSpace. * Minor refactor FastLocalBloomBitsBuilder for new base class XXH3pFilterBitsBuilder shared with new Standard128RibbonBitsBuilder, which allows the latter to fall back on Bloom construction in some extreme cases. * Mostly updated bloom_test for Ribbon filter, though a test like FullBloomTest::Schema is a next TODO to ensure schema stability (in case this becomes production-ready schema as it is). * Add some APIs to ribbon_impl.h for configuring Ribbon filters. Although these are reasonably covered by bloom_test, TODO more unit tests in ribbon_test * Added a "tool" FindOccupancyForSuccessRate to ribbon_test to get data for constructing the linear approximations in GetNumSlotsFor95PctSuccess. Pull Request resolved: https://github.com/facebook/rocksdb/pull/7658 Test Plan: Some unit tests updated but other testing is left TODO. This is considered experimental but laying down schema compatibility as early as possible in case it proves production-quality. Also tested in stress/crash test. Reviewed By: jay-zhuang Differential Revision: D24899349 Pulled By: pdillinger fbshipit-source-id: 9715f3e6371c959d923aea8077c9423c7a9f82b8
Diffstat (limited to 'util')
-rw-r--r--util/bloom_test.cc20
-rw-r--r--util/filter_bench.cc7
-rw-r--r--util/ribbon_impl.h195
-rw-r--r--util/ribbon_test.cc87
4 files changed, 288 insertions, 21 deletions
diff --git a/util/bloom_test.cc b/util/bloom_test.cc
index 2c671794a..4eab70280 100644
--- a/util/bloom_test.cc
+++ b/util/bloom_test.cc
@@ -381,7 +381,8 @@ class FullBloomTest : public testing::TestWithParam<BloomFilterPolicy::Mode> {
case BloomFilterPolicy::kFastLocalBloom:
return for_fast_local_bloom;
case BloomFilterPolicy::kDeprecatedBlock:
- case BloomFilterPolicy::kAuto:
+ case BloomFilterPolicy::kAutoBloom:
+ case BloomFilterPolicy::kStandard128Ribbon:
/* N/A */;
}
// otherwise
@@ -473,7 +474,7 @@ TEST_P(FullBloomTest, FullVaryingLengths) {
}
Build();
- ASSERT_LE(FilterSize(),
+ EXPECT_LE(FilterSize(),
(size_t)((length * 10 / 8) + CACHE_LINE_SIZE * 2 + 5));
// All added keys must match
@@ -488,7 +489,7 @@ TEST_P(FullBloomTest, FullVaryingLengths) {
fprintf(stderr, "False positives: %5.2f%% @ length = %6d ; bytes = %6d\n",
rate*100.0, length, static_cast<int>(FilterSize()));
}
- ASSERT_LE(rate, 0.02); // Must not be over 2%
+ EXPECT_LE(rate, 0.02); // Must not be over 2%
if (rate > 0.0125)
mediocre_filters++; // Allowed, but not too often
else
@@ -498,10 +499,14 @@ TEST_P(FullBloomTest, FullVaryingLengths) {
fprintf(stderr, "Filters: %d good, %d mediocre\n",
good_filters, mediocre_filters);
}
- ASSERT_LE(mediocre_filters, good_filters/5);
+ EXPECT_LE(mediocre_filters, good_filters / 5);
}
TEST_P(FullBloomTest, OptimizeForMemory) {
+ if (GetParam() == BloomFilterPolicy::kStandard128Ribbon) {
+ // TODO Not yet implemented
+ return;
+ }
char buffer[sizeof(int)];
for (bool offm : {true, false}) {
table_options_.optimize_filters_for_memory = offm;
@@ -596,6 +601,10 @@ inline uint32_t SelectByCacheLineSize(uint32_t for64, uint32_t for128,
// ability to read filters generated using other cache line sizes.
// See RawSchema.
TEST_P(FullBloomTest, Schema) {
+ if (GetParam() == BloomFilterPolicy::kStandard128Ribbon) {
+ // TODO ASAP to ensure schema stability
+ return;
+ }
char buffer[sizeof(int)];
// Use enough keys so that changing bits / key by 1 is guaranteed to
@@ -974,7 +983,8 @@ TEST_P(FullBloomTest, CorruptFilters) {
INSTANTIATE_TEST_CASE_P(Full, FullBloomTest,
testing::Values(BloomFilterPolicy::kLegacyBloom,
- BloomFilterPolicy::kFastLocalBloom));
+ BloomFilterPolicy::kFastLocalBloom,
+ BloomFilterPolicy::kStandard128Ribbon));
} // namespace ROCKSDB_NAMESPACE
diff --git a/util/filter_bench.cc b/util/filter_bench.cc
index 7aaf30a73..3761dce75 100644
--- a/util/filter_bench.cc
+++ b/util/filter_bench.cc
@@ -80,7 +80,8 @@ DEFINE_bool(new_builder, false,
DEFINE_uint32(impl, 0,
"Select filter implementation. Without -use_plain_table_bloom:"
- "0 = full filter, 1 = block-based filter. With "
+ "0 = legacy full Bloom filter, 1 = block-based Bloom filter, "
+ "2 = format_version 5 Bloom filter, 3 = Ribbon128 filter. With "
"-use_plain_table_bloom: 0 = no locality, 1 = locality.");
DEFINE_bool(net_includes_hashing, false,
@@ -306,9 +307,9 @@ void FilterBench::Go() {
throw std::runtime_error(
"Block-based filter not currently supported by filter_bench");
}
- if (FLAGS_impl > 2) {
+ if (FLAGS_impl > 3) {
throw std::runtime_error(
- "-impl must currently be 0 or 2 for Block-based table");
+ "-impl must currently be 0, 2, or 3 for Block-based table");
}
}
diff --git a/util/ribbon_impl.h b/util/ribbon_impl.h
index ee81d6a1f..aec1b29c2 100644
--- a/util/ribbon_impl.h
+++ b/util/ribbon_impl.h
@@ -179,11 +179,11 @@ class StandardHasher {
// this function) when number of slots is roughly 10k or larger.
//
// The best values for these smash weights might depend on how
- // densely you're packing entries, but this seems to work well for
- // 2% overhead and roughly 50% success probability.
+ // densely you're packing entries, and also kCoeffBits, but this
+ // seems to work well for roughly 95% success probability.
//
- constexpr auto kFrontSmash = kCoeffBits / 3;
- constexpr auto kBackSmash = kCoeffBits / 3;
+ constexpr Index kFrontSmash = kCoeffBits / 4;
+ constexpr Index kBackSmash = kCoeffBits / 4;
Index start = FastRangeGeneric(h, num_starts + kFrontSmash + kBackSmash);
start = std::max(start, kFrontSmash);
start -= kFrontSmash;
@@ -265,11 +265,16 @@ class StandardHasher {
// This is not so much "critical path" code because it can be done in
// parallel (instruction level) with memory lookup.
//
- // There is no evidence that ResultRow needs to be independent from
- // CoeffRow, so we draw from the same bits computed for CoeffRow,
- // which are reasonably independent from Start. (Inlining and common
- // subexpression elimination with GetCoeffRow should make this
+ // ResultRow bits only needs to be independent from CoeffRow bits if
+ // many entries might have the same start location, where "many" is
+ // comparable to number of hash bits or kCoeffBits. If !kUseSmash
+ // and num_starts > kCoeffBits, it is safe and efficient to draw from
+ // the same bits computed for CoeffRow, which are reasonably
+ // independent from Start. (Inlining and common subexpression
+ // elimination with GetCoeffRow should make this
// a single shared multiplication in generated code.)
+ //
+ // TODO: fix & test the kUseSmash case with very small num_starts
Hash a = h * kCoeffAndResultFactor;
// The bits here that are *most* independent of Start are the highest
// order bits (as in Knuth multiplicative hash). To make those the
@@ -432,6 +437,7 @@ class StandardBanding : public StandardHasher<TypesAndSettings> {
StandardBanding(Index num_slots = 0, Index backtrack_size = 0) {
Reset(num_slots, backtrack_size);
}
+
void Reset(Index num_slots, Index backtrack_size = 0) {
if (num_slots == 0) {
// Unusual (TypesAndSettings::kAllowZeroStarts) or "uninitialized"
@@ -456,6 +462,7 @@ class StandardBanding : public StandardHasher<TypesAndSettings> {
}
EnsureBacktrackSize(backtrack_size);
}
+
void EnsureBacktrackSize(Index backtrack_size) {
if (backtrack_size > backtrack_size_) {
backtrack_.reset(new Index[backtrack_size]);
@@ -601,6 +608,54 @@ class StandardBanding : public StandardHasher<TypesAndSettings> {
return false;
}
+ // ********************************************************************
+ // Static high-level API
+
+ // Based on data from FindOccupancyForSuccessRate in ribbon_test,
+ // returns a number of slots for a given number of entries to add
+ // that should have roughly 95% or better chance of successful
+ // construction per seed. Does NOT do rounding for InterleavedSoln;
+ // call RoundUpNumSlots for that.
+ //
+ // num_to_add should not exceed roughly 2/3rds of the maximum value
+ // of the Index type to avoid overflow.
+ static Index GetNumSlotsFor95PctSuccess(Index num_to_add) {
+ if (num_to_add == 0) {
+ return 0;
+ }
+ double factor = GetFactorFor95PctSuccess(num_to_add);
+ Index num_slots = static_cast<Index>(num_to_add * factor);
+ assert(num_slots >= num_to_add);
+ return num_slots;
+ }
+
+ // Based on data from FindOccupancyForSuccessRate in ribbon_test,
+ // given a number of entries to add, returns a space overhead factor
+ // (slots divided by num_to_add) that should have roughly 95% or better
+ // chance of successful construction per seed. Does NOT do rounding for
+ // InterleavedSoln; call RoundUpNumSlots for that.
+ //
+ // The reason that num_to_add is needed is that Ribbon filters of a
+ // particular CoeffRow size do not scale infinitely.
+ static double GetFactorFor95PctSuccess(Index num_to_add) {
+ double log2_num_to_add = std::log(num_to_add) * 1.442695;
+ if (kCoeffBits == 64) {
+ if (TypesAndSettings::kUseSmash) {
+ return 1.02 + std::max(log2_num_to_add - 8.5, 0.0) * 0.009;
+ } else {
+ return 1.05 + std::max(log2_num_to_add - 11.0, 0.0) * 0.009;
+ }
+ } else {
+ // Currently only support 64 and 128
+ assert(kCoeffBits == 128);
+ if (TypesAndSettings::kUseSmash) {
+ return 1.01 + std::max(log2_num_to_add - 10.0, 0.0) * 0.0042;
+ } else {
+ return 1.02 + std::max(log2_num_to_add - 12.0, 0.0) * 0.0042;
+ }
+ }
+ }
+
protected:
// TODO: explore combining in a struct
std::unique_ptr<CoeffRow[]> coeff_rows_;
@@ -759,6 +814,19 @@ class SerializableInterleavedSolution {
// ********************************************************************
// High-level API
+ void ConfigureForNumBlocks(Index num_blocks) {
+ if (num_blocks == 0) {
+ PrepareForNumStarts(0);
+ } else {
+ PrepareForNumStarts(num_blocks * kCoeffBits - kCoeffBits + 1);
+ }
+ }
+
+ void ConfigureForNumSlots(Index num_slots) {
+ assert(num_slots % kCoeffBits == 0);
+ ConfigureForNumBlocks(num_slots / kCoeffBits);
+ }
+
template <typename BandingStorage>
void BackSubstFrom(const BandingStorage& bs) {
if (TypesAndSettings::kAllowZeroStarts && bs.GetNumStarts() == 0) {
@@ -805,7 +873,7 @@ class SerializableInterleavedSolution {
// Note: Ignoring smash setting; still close enough in that case
double lower_portion =
- (upper_start_block_ * kCoeffBits * 1.0) / num_starts_;
+ (upper_start_block_ * 1.0 * kCoeffBits) / num_starts_;
// Each result (solution) bit (column) cuts FP rate in half. Weight that
// for upper and lower number of bits (columns).
@@ -813,7 +881,112 @@ class SerializableInterleavedSolution {
(1.0 - lower_portion) * std::pow(0.5, upper_num_columns_);
}
+ // ********************************************************************
+ // Static high-level API
+
+ // Round up to a number of slots supported by this structure. Note that
+ // this needs to be must be taken into account for the banding if this
+ // solution layout/storage is to be used.
+ static Index RoundUpNumSlots(Index num_slots) {
+ // Must be multiple of kCoeffBits
+ Index corrected = (num_slots + kCoeffBits - 1) / kCoeffBits * kCoeffBits;
+
+ // Do not use num_starts==1 unless kUseSmash, because the hashing
+ // might not be equipped for stacking up so many entries on a
+ // single start location.
+ if (!TypesAndSettings::kUseSmash && corrected == kCoeffBits) {
+ corrected += kCoeffBits;
+ }
+ return corrected;
+ }
+
+ // Compute the number of bytes for a given number of slots and desired
+ // FP rate. Since desired FP rate might not be exactly achievable,
+ // rounding_bias32==0 means to always round toward lower FP rate
+ // than desired (more bytes); rounding_bias32==max uint32_t means always
+ // round toward higher FP rate than desired (fewer bytes); other values
+ // act as a proportional threshold or bias between the two.
+ static size_t GetBytesForFpRate(Index num_slots, double desired_fp_rate,
+ uint32_t rounding_bias32) {
+ return InternalGetBytesForFpRate(num_slots, desired_fp_rate,
+ 1.0 / desired_fp_rate, rounding_bias32);
+ }
+
+ // The same, but specifying desired accuracy as 1.0 / FP rate, or
+ // one_in_fp_rate. E.g. desired_one_in_fp_rate=100 means 1% FP rate.
+ static size_t GetBytesForOneInFpRate(Index num_slots,
+ double desired_one_in_fp_rate,
+ uint32_t rounding_bias32) {
+ return InternalGetBytesForFpRate(num_slots, 1.0 / desired_one_in_fp_rate,
+ desired_one_in_fp_rate, rounding_bias32);
+ }
+
protected:
+ static size_t InternalGetBytesForFpRate(Index num_slots,
+ double desired_fp_rate,
+ double desired_one_in_fp_rate,
+ uint32_t rounding_bias32) {
+ assert(TypesAndSettings::kIsFilter);
+ if (TypesAndSettings::kAllowZeroStarts && num_slots == 0) {
+ // Unusual. Zero starts presumes no keys added -> always false (no FPs)
+ return 0U;
+ }
+ // Must be rounded up already.
+ assert(RoundUpNumSlots(num_slots) == num_slots);
+
+ if (desired_one_in_fp_rate > 1.0 && desired_fp_rate < 1.0) {
+ // Typical: less than 100% FP rate
+ if (desired_one_in_fp_rate <= static_cast<ResultRow>(-1)) {
+ // Typical: Less than maximum result row entropy
+ ResultRow rounded = static_cast<ResultRow>(desired_one_in_fp_rate);
+ int lower_columns = FloorLog2(rounded);
+ double lower_columns_fp_rate = std::pow(2.0, -lower_columns);
+ double upper_columns_fp_rate = std::pow(2.0, -(lower_columns + 1));
+ // Floating point don't let me down!
+ assert(lower_columns_fp_rate >= desired_fp_rate);
+ assert(upper_columns_fp_rate <= desired_fp_rate);
+
+ double lower_portion = (desired_fp_rate - upper_columns_fp_rate) /
+ (lower_columns_fp_rate - upper_columns_fp_rate);
+ // Floating point don't let me down!
+ assert(lower_portion >= 0.0);
+ assert(lower_portion <= 1.0);
+
+ double rounding_bias = (rounding_bias32 + 0.5) / double{0x100000000};
+ assert(rounding_bias > 0.0);
+ assert(rounding_bias < 1.0);
+
+ // Note: Ignoring smash setting; still close enough in that case
+ Index num_starts = num_slots - kCoeffBits + 1;
+ // Lower upper_start_block means lower FP rate (higher accuracy)
+ Index upper_start_block = static_cast<Index>(
+ (lower_portion * num_starts + rounding_bias) / kCoeffBits);
+ Index num_blocks = num_slots / kCoeffBits;
+ assert(upper_start_block < num_blocks);
+
+ // Start by assuming all blocks use lower number of columns
+ Index num_segments = num_blocks * static_cast<Index>(lower_columns);
+ // Correct by 1 each for blocks using upper number of columns
+ num_segments += (num_blocks - upper_start_block);
+ // Total bytes
+ return num_segments * sizeof(CoeffRow);
+ } else {
+ // one_in_fp_rate too big, thus requested FP rate is smaller than
+ // supported. Use max number of columns for minimum supported FP rate.
+ return num_slots * sizeof(ResultRow);
+ }
+ } else {
+ // Effectively asking for 100% FP rate, or NaN etc.
+ if (TypesAndSettings::kAllowZeroStarts) {
+ // Zero segments
+ return 0U;
+ } else {
+ // One segment (minimum size, maximizing FP rate)
+ return sizeof(CoeffRow);
+ }
+ }
+ }
+
void InternalConfigure() {
const Index num_blocks = GetNumBlocks();
Index num_segments = GetNumSegments();
@@ -842,11 +1015,11 @@ class SerializableInterleavedSolution {
data_len_ = num_segments * sizeof(CoeffRow);
}
+ char* const data_;
+ size_t data_len_;
Index num_starts_ = 0;
Index upper_num_columns_ = 0;
Index upper_start_block_ = 0;
- char* const data_;
- size_t data_len_;
};
} // namespace ribbon
diff --git a/util/ribbon_test.cc b/util/ribbon_test.cc
index 00dda42a0..9067c9719 100644
--- a/util/ribbon_test.cc
+++ b/util/ribbon_test.cc
@@ -14,12 +14,36 @@
#ifndef GFLAGS
uint32_t FLAGS_thoroughness = 5;
+bool FLAGS_find_occ = false;
+double FLAGS_find_next_factor = 1.414;
+double FLAGS_find_success = 0.95;
+double FLAGS_find_delta_start = 0.01;
+double FLAGS_find_delta_end = 0.0001;
+double FLAGS_find_delta_shrink = 0.99;
+uint32_t FLAGS_find_min_slots = 128;
+uint32_t FLAGS_find_max_slots = 12800000;
#else
#include "util/gflags_compat.h"
using GFLAGS_NAMESPACE::ParseCommandLineFlags;
// Using 500 is a good test when you have time to be thorough.
// Default is for general RocksDB regression test runs.
DEFINE_uint32(thoroughness, 5, "iterations per configuration");
+
+// Options for FindOccupancyForSuccessRate, which is more of a tool
+// than a test.
+DEFINE_bool(find_occ, false,
+ "whether to run the FindOccupancyForSuccessRate tool");
+DEFINE_double(find_next_factor, 1.414,
+ "target success rate for FindOccupancyForSuccessRate");
+DEFINE_double(find_success, 0.95,
+ "target success rate for FindOccupancyForSuccessRate");
+DEFINE_double(find_delta_start, 0.01, " for FindOccupancyForSuccessRate");
+DEFINE_double(find_delta_end, 0.0001, " for FindOccupancyForSuccessRate");
+DEFINE_double(find_delta_shrink, 0.99, " for FindOccupancyForSuccessRate");
+DEFINE_uint32(find_min_slots, 128,
+ "number of slots for FindOccupancyForSuccessRate");
+DEFINE_uint32(find_max_slots, 12800000,
+ "number of slots for FindOccupancyForSuccessRate");
#endif // GFLAGS
template <typename TypesAndSettings>
@@ -44,6 +68,11 @@ struct StandardKeyGen {
return *this;
}
+ StandardKeyGen& operator+=(uint64_t i) {
+ id_ += i;
+ return *this;
+ }
+
const std::string& operator*() {
// Use multiplication to mix things up a little in the key
ROCKSDB_NAMESPACE::EncodeFixed64(&str_[str_.size() - 8],
@@ -81,6 +110,11 @@ struct SmallKeyGen {
return *this;
}
+ SmallKeyGen& operator+=(uint64_t i) {
+ id_ += i;
+ return *this;
+ }
+
const std::string& operator*() {
ROCKSDB_NAMESPACE::EncodeFixed64(&str_[str_.size() - 8], id_);
return str_;
@@ -325,8 +359,8 @@ TYPED_TEST(RibbonTypeParamTest, CompactnessAndBacktrackAndFpRate) {
Index num_slots = static_cast<Index>(num_to_add * kFactor);
if (test_interleaved) {
- // Round to nearest multiple of kCoeffBits
- num_slots = ((num_slots + kCoeffBits / 2) / kCoeffBits) * kCoeffBits;
+ // Round to supported number of slots
+ num_slots = InterleavedSoln::RoundUpNumSlots(num_slots);
// Re-adjust num_to_add to get as close as possible to kFactor
num_to_add = static_cast<uint32_t>(num_slots / kFactor);
}
@@ -839,6 +873,55 @@ TEST(RibbonTest, PhsfBasic) {
}
}
+// Not a real test, but a tool used to build GetNumSlotsFor95PctSuccess
+TYPED_TEST(RibbonTypeParamTest, FindOccupancyForSuccessRate) {
+ IMPORT_RIBBON_TYPES_AND_SETTINGS(TypeParam);
+ IMPORT_RIBBON_IMPL_TYPES(TypeParam);
+ using KeyGen = typename TypeParam::KeyGen;
+
+ if (!FLAGS_find_occ) {
+ fprintf(stderr, "Tool disabled during unit test runs\n");
+ return;
+ }
+
+ KeyGen cur("blah", 0);
+
+ Banding banding;
+ Index num_slots = InterleavedSoln::RoundUpNumSlots(FLAGS_find_min_slots);
+ while (num_slots < FLAGS_find_max_slots) {
+ double factor = 0.95;
+ double delta = FLAGS_find_delta_start;
+ while (delta > FLAGS_find_delta_end) {
+ Index num_to_add = static_cast<Index>(factor * num_slots);
+ KeyGen end = cur;
+ end += num_to_add;
+ bool success = banding.ResetAndFindSeedToSolve(num_slots, cur, end, 0, 0);
+ cur = end; // fresh keys
+ if (success) {
+ factor += delta * (1.0 - FLAGS_find_success);
+ factor = std::min(factor, 1.0);
+ } else {
+ factor -= delta * FLAGS_find_success;
+ factor = std::max(factor, 0.0);
+ }
+ delta *= FLAGS_find_delta_shrink;
+ fprintf(stderr,
+ "slots: %u log2_slots: %g target_success: %g ->overhead: %g\r",
+ static_cast<unsigned>(num_slots),
+ std::log(num_slots * 1.0) / std::log(2.0), FLAGS_find_success,
+ 1.0 / factor);
+ }
+ fprintf(stderr, "\n");
+
+ num_slots = std::max(
+ num_slots + 1, static_cast<Index>(num_slots * FLAGS_find_next_factor));
+ num_slots = InterleavedSoln::RoundUpNumSlots(num_slots);
+ }
+}
+
+// TODO: unit tests for configuration APIs
+// TODO: unit tests for small filter FP rates
+
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
#ifdef GFLAGS